feat: initialize mini program map engine
This commit is contained in:
234
miniprogram/engine/renderer/webglVectorRenderer.ts
Normal file
234
miniprogram/engine/renderer/webglVectorRenderer.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { buildCamera, type MapScene } from './mapRenderer'
|
||||
import { TrackLayer } from '../layer/trackLayer'
|
||||
import { GpsLayer } from '../layer/gpsLayer'
|
||||
|
||||
function createShader(gl: any, type: number, source: string): any {
|
||||
const shader = gl.createShader(type)
|
||||
if (!shader) {
|
||||
throw new Error('WebGL shader 创建失败')
|
||||
}
|
||||
|
||||
gl.shaderSource(shader, source)
|
||||
gl.compileShader(shader)
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
const message = gl.getShaderInfoLog(shader) || 'unknown shader error'
|
||||
gl.deleteShader(shader)
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
return shader
|
||||
}
|
||||
|
||||
function createProgram(gl: any, vertexSource: string, fragmentSource: string): any {
|
||||
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource)
|
||||
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
|
||||
const program = gl.createProgram()
|
||||
if (!program) {
|
||||
throw new Error('WebGL program 创建失败')
|
||||
}
|
||||
|
||||
gl.attachShader(program, vertexShader)
|
||||
gl.attachShader(program, fragmentShader)
|
||||
gl.linkProgram(program)
|
||||
gl.deleteShader(vertexShader)
|
||||
gl.deleteShader(fragmentShader)
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
const message = gl.getProgramInfoLog(program) || 'unknown program error'
|
||||
gl.deleteProgram(program)
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
|
||||
export class WebGLVectorRenderer {
|
||||
canvas: any
|
||||
gl: any
|
||||
dpr: number
|
||||
trackLayer: TrackLayer
|
||||
gpsLayer: GpsLayer
|
||||
program: any
|
||||
positionBuffer: any
|
||||
colorBuffer: any
|
||||
positionLocation: number
|
||||
colorLocation: number
|
||||
|
||||
constructor(trackLayer: TrackLayer, gpsLayer: GpsLayer) {
|
||||
this.canvas = null
|
||||
this.gl = null
|
||||
this.dpr = 1
|
||||
this.trackLayer = trackLayer
|
||||
this.gpsLayer = gpsLayer
|
||||
this.program = null
|
||||
this.positionBuffer = null
|
||||
this.colorBuffer = null
|
||||
this.positionLocation = -1
|
||||
this.colorLocation = -1
|
||||
}
|
||||
|
||||
attachCanvas(canvasNode: any, width: number, height: number, dpr: number): void {
|
||||
this.canvas = canvasNode
|
||||
this.dpr = dpr || 1
|
||||
canvasNode.width = Math.max(1, Math.floor(width * this.dpr))
|
||||
canvasNode.height = Math.max(1, Math.floor(height * this.dpr))
|
||||
this.attachContext(canvasNode.getContext('webgl') || canvasNode.getContext('experimental-webgl'), canvasNode)
|
||||
}
|
||||
|
||||
attachContext(gl: any, canvasNode: any): void {
|
||||
if (!gl) {
|
||||
throw new Error('当前环境不支持 WebGL Vector Layer')
|
||||
}
|
||||
|
||||
this.canvas = canvasNode
|
||||
this.gl = gl
|
||||
this.program = createProgram(
|
||||
gl,
|
||||
'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; }',
|
||||
'precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; }',
|
||||
)
|
||||
this.positionBuffer = gl.createBuffer()
|
||||
this.colorBuffer = gl.createBuffer()
|
||||
this.positionLocation = gl.getAttribLocation(this.program, 'a_position')
|
||||
this.colorLocation = gl.getAttribLocation(this.program, 'a_color')
|
||||
|
||||
gl.viewport(0, 0, canvasNode.width, canvasNode.height)
|
||||
gl.enable(gl.BLEND)
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.gl) {
|
||||
if (this.program) {
|
||||
this.gl.deleteProgram(this.program)
|
||||
}
|
||||
if (this.positionBuffer) {
|
||||
this.gl.deleteBuffer(this.positionBuffer)
|
||||
}
|
||||
if (this.colorBuffer) {
|
||||
this.gl.deleteBuffer(this.colorBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
this.program = null
|
||||
this.positionBuffer = null
|
||||
this.colorBuffer = null
|
||||
this.gl = null
|
||||
this.canvas = null
|
||||
}
|
||||
|
||||
render(scene: MapScene, pulseFrame: number): void {
|
||||
if (!this.gl || !this.program || !this.positionBuffer || !this.colorBuffer || !this.canvas) {
|
||||
return
|
||||
}
|
||||
|
||||
const gl = this.gl
|
||||
const camera = buildCamera(scene)
|
||||
const trackPoints = this.trackLayer.projectPoints(scene, camera)
|
||||
const gpsPoint = this.gpsLayer.projectPoint(scene, camera)
|
||||
const positions: number[] = []
|
||||
const colors: number[] = []
|
||||
|
||||
for (let index = 1; index < trackPoints.length; index += 1) {
|
||||
this.pushSegment(positions, colors, trackPoints[index - 1], trackPoints[index], 6, [0.09, 0.43, 0.36, 0.96], scene)
|
||||
}
|
||||
|
||||
for (const point of trackPoints) {
|
||||
this.pushCircle(positions, colors, point.x, point.y, 10, [0.09, 0.43, 0.36, 1], scene)
|
||||
this.pushCircle(positions, colors, point.x, point.y, 6.5, [0.97, 0.98, 0.95, 1], scene)
|
||||
}
|
||||
|
||||
this.pushCircle(positions, colors, gpsPoint.x, gpsPoint.y, this.gpsLayer.getPulseRadius(pulseFrame), [0.13, 0.62, 0.74, 0.22], scene)
|
||||
this.pushCircle(positions, colors, gpsPoint.x, gpsPoint.y, 13, [1, 1, 1, 0.95], scene)
|
||||
this.pushCircle(positions, colors, gpsPoint.x, gpsPoint.y, 9, [0.13, 0.63, 0.74, 1], scene)
|
||||
|
||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height)
|
||||
gl.useProgram(this.program)
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer)
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STREAM_DRAW)
|
||||
gl.enableVertexAttribArray(this.positionLocation)
|
||||
gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer)
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STREAM_DRAW)
|
||||
gl.enableVertexAttribArray(this.colorLocation)
|
||||
gl.vertexAttribPointer(this.colorLocation, 4, gl.FLOAT, false, 0, 0)
|
||||
|
||||
gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2)
|
||||
}
|
||||
|
||||
pushSegment(
|
||||
positions: number[],
|
||||
colors: number[],
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number },
|
||||
width: number,
|
||||
color: [number, number, number, number],
|
||||
scene: MapScene,
|
||||
): void {
|
||||
const deltaX = end.x - start.x
|
||||
const deltaY = end.y - start.y
|
||||
const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
if (!length) {
|
||||
return
|
||||
}
|
||||
|
||||
const normalX = -deltaY / length * (width / 2)
|
||||
const normalY = deltaX / length * (width / 2)
|
||||
const topLeft = this.toClip(start.x + normalX, start.y + normalY, scene)
|
||||
const topRight = this.toClip(end.x + normalX, end.y + normalY, scene)
|
||||
const bottomLeft = this.toClip(start.x - normalX, start.y - normalY, scene)
|
||||
const bottomRight = this.toClip(end.x - normalX, end.y - normalY, scene)
|
||||
|
||||
this.pushTriangle(positions, colors, topLeft, topRight, bottomLeft, color)
|
||||
this.pushTriangle(positions, colors, bottomLeft, topRight, bottomRight, color)
|
||||
}
|
||||
|
||||
pushCircle(
|
||||
positions: number[],
|
||||
colors: number[],
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
radius: number,
|
||||
color: [number, number, number, number],
|
||||
scene: MapScene,
|
||||
): void {
|
||||
const segments = 20
|
||||
const center = this.toClip(centerX, centerY, scene)
|
||||
for (let index = 0; index < segments; index += 1) {
|
||||
const startAngle = index / segments * Math.PI * 2
|
||||
const endAngle = (index + 1) / segments * Math.PI * 2
|
||||
const start = this.toClip(centerX + Math.cos(startAngle) * radius, centerY + Math.sin(startAngle) * radius, scene)
|
||||
const end = this.toClip(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius, scene)
|
||||
this.pushTriangle(positions, colors, center, start, end, color)
|
||||
}
|
||||
}
|
||||
|
||||
pushTriangle(
|
||||
positions: number[],
|
||||
colors: number[],
|
||||
first: { x: number; y: number },
|
||||
second: { x: number; y: number },
|
||||
third: { x: number; y: number },
|
||||
color: [number, number, number, number],
|
||||
): void {
|
||||
positions.push(first.x, first.y, second.x, second.y, third.x, third.y)
|
||||
for (let index = 0; index < 3; index += 1) {
|
||||
colors.push(color[0], color[1], color[2], color[3])
|
||||
}
|
||||
}
|
||||
|
||||
toClip(x: number, y: number, scene: MapScene): { x: number; y: number } {
|
||||
const previewScale = scene.previewScale || 1
|
||||
const originX = scene.previewOriginX || scene.viewportWidth / 2
|
||||
const originY = scene.previewOriginY || scene.viewportHeight / 2
|
||||
const scaledX = originX + (x - originX) * previewScale
|
||||
const scaledY = originY + (y - originY) * previewScale
|
||||
|
||||
return {
|
||||
x: scaledX / scene.viewportWidth * 2 - 1,
|
||||
y: 1 - scaledY / scene.viewportHeight * 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user