| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689 |
- import { getTileSizePx, type CameraState } from '../camera/camera'
- import { worldTileToLonLat } from '../../utils/projection'
- import { type MapScene } from './mapRenderer'
- import { CourseLayer, type ProjectedCourseLayers, type ProjectedCourseLeg } from '../layer/courseLayer'
- import { TrackLayer } from '../layer/trackLayer'
- import { GpsLayer } from '../layer/gpsLayer'
- const COURSE_COLOR: [number, number, number, number] = [0.8, 0.0, 0.42, 0.96]
- const COMPLETED_ROUTE_COLOR: [number, number, number, number] = [0.48, 0.5, 0.54, 0.82]
- const ACTIVE_CONTROL_COLOR: [number, number, number, number] = [0.22, 1, 0.95, 1]
- const ACTIVE_LEG_COLOR: [number, number, number, number] = [0.18, 1, 0.94, 0.5]
- const EARTH_CIRCUMFERENCE_METERS = 40075016.686
- const CONTROL_RING_WIDTH_RATIO = 0.2
- const FINISH_INNER_RADIUS_RATIO = 0.6
- const FINISH_RING_WIDTH_RATIO = 0.2
- const START_RING_WIDTH_RATIO = 0.2
- const LEG_WIDTH_RATIO = 0.2
- const LEG_TRIM_TO_RING_CENTER_RATIO = 1 - CONTROL_RING_WIDTH_RATIO / 2
- const ACTIVE_CONTROL_PULSE_SPEED = 0.18
- const ACTIVE_CONTROL_PULSE_MIN_SCALE = 1.12
- const ACTIVE_CONTROL_PULSE_MAX_SCALE = 1.46
- const ACTIVE_CONTROL_PULSE_WIDTH_RATIO = 0.12
- const GUIDE_FLOW_COUNT = 5
- const GUIDE_FLOW_SPEED = 0.02
- const GUIDE_FLOW_TRAIL = 0.16
- const GUIDE_FLOW_MIN_WIDTH_RATIO = 0.12
- const GUIDE_FLOW_MAX_WIDTH_RATIO = 0.22
- const GUIDE_FLOW_HEAD_RADIUS_RATIO = 0.18
- type RgbaColor = [number, number, number, number]
- 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
- courseLayer: CourseLayer
- trackLayer: TrackLayer
- gpsLayer: GpsLayer
- program: any
- positionBuffer: any
- colorBuffer: any
- positionLocation: number
- colorLocation: number
- constructor(courseLayer: CourseLayer, trackLayer: TrackLayer, gpsLayer: GpsLayer) {
- this.canvas = null
- this.gl = null
- this.dpr = 1
- this.courseLayer = courseLayer
- 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 course = this.courseLayer.projectCourse(scene)
- const trackPoints = this.trackLayer.projectPoints(scene)
- const gpsPoint = this.gpsLayer.projectPoint(scene)
- const positions: number[] = []
- const colors: number[] = []
- if (course) {
- this.pushCourse(positions, colors, course, scene, pulseFrame)
- }
- 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)
- }
- if (gpsPoint) {
- 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)
- }
- if (!positions.length) {
- return
- }
- 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)
- }
- getPixelsPerMeter(scene: MapScene): number {
- const camera: CameraState = {
- centerWorldX: scene.exactCenterWorldX,
- centerWorldY: scene.exactCenterWorldY,
- viewportWidth: scene.viewportWidth,
- viewportHeight: scene.viewportHeight,
- visibleColumns: scene.visibleColumns,
- }
- const tileSizePx = getTileSizePx(camera)
- const centerLonLat = worldTileToLonLat({ x: scene.exactCenterWorldX, y: scene.exactCenterWorldY }, scene.zoom)
- const metersPerTile = Math.cos(centerLonLat.lat * Math.PI / 180) * EARTH_CIRCUMFERENCE_METERS / Math.pow(2, scene.zoom)
- if (!tileSizePx || !metersPerTile) {
- return 0
- }
- return tileSizePx / metersPerTile
- }
- getMetric(scene: MapScene, meters: number): number {
- return meters * this.getPixelsPerMeter(scene)
- }
- getControlRadiusMeters(scene: MapScene): number {
- return scene.cpRadiusMeters > 0 ? scene.cpRadiusMeters : 5
- }
- pushCourse(
- positions: number[],
- colors: number[],
- course: ProjectedCourseLayers,
- scene: MapScene,
- pulseFrame: number,
- ): void {
- const controlRadiusMeters = this.getControlRadiusMeters(scene)
- if (scene.revealFullCourse) {
- for (let index = 0; index < course.legs.length; index += 1) {
- const leg = course.legs[index]
- this.pushCourseLeg(positions, colors, leg, controlRadiusMeters, this.getLegColor(scene, index), scene)
- if (scene.activeLegIndices.includes(index)) {
- this.pushCourseLegHighlight(positions, colors, leg, controlRadiusMeters, scene)
- }
- }
- const guideLeg = this.getGuideLeg(course, scene)
- if (guideLeg) {
- this.pushGuidanceFlow(positions, colors, guideLeg, controlRadiusMeters, scene, pulseFrame)
- }
- }
- for (const start of course.starts) {
- if (scene.activeStart) {
- this.pushActiveStartPulse(positions, colors, start.point.x, start.point.y, start.headingDeg, controlRadiusMeters, scene, pulseFrame)
- }
- this.pushStartTriangle(positions, colors, start.point.x, start.point.y, start.headingDeg, controlRadiusMeters, this.getStartColor(scene), scene)
- }
- if (!scene.revealFullCourse) {
- return
- }
- for (const control of course.controls) {
- if (scene.activeControlSequences.includes(control.sequence)) {
- this.pushActiveControlPulse(positions, colors, control.point.x, control.point.y, controlRadiusMeters, scene, pulseFrame)
- }
- this.pushRing(
- positions,
- colors,
- control.point.x,
- control.point.y,
- this.getMetric(scene, controlRadiusMeters),
- this.getMetric(scene, controlRadiusMeters * (1 - CONTROL_RING_WIDTH_RATIO)),
- this.getControlColor(scene, control.sequence),
- scene,
- )
- }
- for (const finish of course.finishes) {
- if (scene.activeFinish) {
- this.pushActiveControlPulse(positions, colors, finish.point.x, finish.point.y, controlRadiusMeters, scene, pulseFrame)
- }
- const finishColor = this.getFinishColor(scene)
- this.pushRing(
- positions,
- colors,
- finish.point.x,
- finish.point.y,
- this.getMetric(scene, controlRadiusMeters),
- this.getMetric(scene, controlRadiusMeters * (1 - FINISH_RING_WIDTH_RATIO)),
- finishColor,
- scene,
- )
- this.pushRing(
- positions,
- colors,
- finish.point.x,
- finish.point.y,
- this.getMetric(scene, controlRadiusMeters * FINISH_INNER_RADIUS_RATIO),
- this.getMetric(scene, controlRadiusMeters * FINISH_INNER_RADIUS_RATIO * (1 - FINISH_RING_WIDTH_RATIO / FINISH_INNER_RADIUS_RATIO)),
- finishColor,
- scene,
- )
- }
- }
- getGuideLeg(course: ProjectedCourseLayers, scene: MapScene): ProjectedCourseLeg | null {
- const activeIndex = scene.activeLegIndices.length ? scene.activeLegIndices[0] : -1
- if (activeIndex >= 0 && activeIndex < course.legs.length) {
- return course.legs[activeIndex]
- }
- return null
- }
- getLegColor(scene: MapScene, index: number): RgbaColor {
- return this.isCompletedLeg(scene, index) ? COMPLETED_ROUTE_COLOR : COURSE_COLOR
- }
- isCompletedLeg(scene: MapScene, index: number): boolean {
- return scene.completedLegIndices.includes(index)
- }
- pushCourseLeg(
- positions: number[],
- colors: number[],
- leg: ProjectedCourseLeg,
- controlRadiusMeters: number,
- color: RgbaColor,
- scene: MapScene,
- ): void {
- const trimmed = this.getTrimmedCourseLeg(leg, controlRadiusMeters, scene)
- if (!trimmed) {
- return
- }
- this.pushSegment(positions, colors, trimmed.from, trimmed.to, this.getMetric(scene, controlRadiusMeters * LEG_WIDTH_RATIO), color, scene)
- }
- pushCourseLegHighlight(
- positions: number[],
- colors: number[],
- leg: ProjectedCourseLeg,
- controlRadiusMeters: number,
- scene: MapScene,
- ): void {
- const trimmed = this.getTrimmedCourseLeg(leg, controlRadiusMeters, scene)
- if (!trimmed) {
- return
- }
- this.pushSegment(
- positions,
- colors,
- trimmed.from,
- trimmed.to,
- this.getMetric(scene, controlRadiusMeters * LEG_WIDTH_RATIO * 1.5),
- ACTIVE_LEG_COLOR,
- scene,
- )
- }
- pushActiveControlPulse(
- positions: number[],
- colors: number[],
- centerX: number,
- centerY: number,
- controlRadiusMeters: number,
- scene: MapScene,
- pulseFrame: number,
- ): void {
- const pulse = (Math.sin(pulseFrame * ACTIVE_CONTROL_PULSE_SPEED) + 1) / 2
- const pulseScale = ACTIVE_CONTROL_PULSE_MIN_SCALE + (ACTIVE_CONTROL_PULSE_MAX_SCALE - ACTIVE_CONTROL_PULSE_MIN_SCALE) * pulse
- const pulseWidthScale = pulseScale - ACTIVE_CONTROL_PULSE_WIDTH_RATIO
- const glowAlpha = 0.24 + pulse * 0.34
- const glowColor: RgbaColor = [0.36, 1, 0.96, glowAlpha]
- this.pushRing(
- positions,
- colors,
- centerX,
- centerY,
- this.getMetric(scene, controlRadiusMeters * pulseScale),
- this.getMetric(scene, controlRadiusMeters * Math.max(1, pulseWidthScale)),
- glowColor,
- scene,
- )
- }
- pushActiveStartPulse(
- positions: number[],
- colors: number[],
- centerX: number,
- centerY: number,
- headingDeg: number | null,
- controlRadiusMeters: number,
- scene: MapScene,
- pulseFrame: number,
- ): void {
- const pulse = (Math.sin(pulseFrame * ACTIVE_CONTROL_PULSE_SPEED) + 1) / 2
- const pulseScale = ACTIVE_CONTROL_PULSE_MIN_SCALE + (ACTIVE_CONTROL_PULSE_MAX_SCALE - ACTIVE_CONTROL_PULSE_MIN_SCALE) * pulse
- const pulseWidthScale = pulseScale - ACTIVE_CONTROL_PULSE_WIDTH_RATIO
- const glowAlpha = 0.24 + pulse * 0.34
- const glowColor: RgbaColor = [0.36, 1, 0.96, glowAlpha]
- const headingRad = ((headingDeg === null ? 0 : headingDeg) - 90) * Math.PI / 180
- const ringCenterX = centerX + Math.cos(headingRad) * this.getMetric(scene, controlRadiusMeters * 0.04)
- const ringCenterY = centerY + Math.sin(headingRad) * this.getMetric(scene, controlRadiusMeters * 0.04)
- this.pushRing(
- positions,
- colors,
- ringCenterX,
- ringCenterY,
- this.getMetric(scene, controlRadiusMeters * pulseScale),
- this.getMetric(scene, controlRadiusMeters * Math.max(1, pulseWidthScale)),
- glowColor,
- scene,
- )
- }
- getStartColor(scene: MapScene): RgbaColor {
- if (scene.activeStart) {
- return ACTIVE_CONTROL_COLOR
- }
- if (scene.completedStart) {
- return COMPLETED_ROUTE_COLOR
- }
- return COURSE_COLOR
- }
- getControlColor(scene: MapScene, sequence: number): RgbaColor {
- if (scene.activeControlSequences.includes(sequence)) {
- return ACTIVE_CONTROL_COLOR
- }
- if (scene.completedControlSequences.includes(sequence)) {
- return COMPLETED_ROUTE_COLOR
- }
- return COURSE_COLOR
- }
- getFinishColor(scene: MapScene): RgbaColor {
- if (scene.activeFinish) {
- return ACTIVE_CONTROL_COLOR
- }
- if (scene.completedFinish) {
- return COMPLETED_ROUTE_COLOR
- }
- return COURSE_COLOR
- }
- pushGuidanceFlow(
- positions: number[],
- colors: number[],
- leg: ProjectedCourseLeg,
- controlRadiusMeters: number,
- scene: MapScene,
- pulseFrame: number,
- ): void {
- const trimmed = this.getTrimmedCourseLeg(leg, controlRadiusMeters, scene)
- if (!trimmed) {
- return
- }
- const dx = trimmed.to.x - trimmed.from.x
- const dy = trimmed.to.y - trimmed.from.y
- const length = Math.sqrt(dx * dx + dy * dy)
- if (!length) {
- return
- }
- for (let index = 0; index < GUIDE_FLOW_COUNT; index += 1) {
- const progress = (pulseFrame * GUIDE_FLOW_SPEED + index / GUIDE_FLOW_COUNT) % 1
- const tailProgress = Math.max(0, progress - GUIDE_FLOW_TRAIL)
- const head = {
- x: trimmed.from.x + dx * progress,
- y: trimmed.from.y + dy * progress,
- }
- const tail = {
- x: trimmed.from.x + dx * tailProgress,
- y: trimmed.from.y + dy * tailProgress,
- }
- const eased = progress * progress
- const width = this.getMetric(
- scene,
- controlRadiusMeters * (GUIDE_FLOW_MIN_WIDTH_RATIO + (GUIDE_FLOW_MAX_WIDTH_RATIO - GUIDE_FLOW_MIN_WIDTH_RATIO) * eased),
- )
- const outerColor = this.getGuideFlowOuterColor(eased)
- const innerColor = this.getGuideFlowInnerColor(eased)
- const headRadius = this.getMetric(scene, controlRadiusMeters * GUIDE_FLOW_HEAD_RADIUS_RATIO * (0.72 + eased * 0.42))
- this.pushSegment(positions, colors, tail, head, width * 1.9, outerColor, scene)
- this.pushSegment(positions, colors, tail, head, width, innerColor, scene)
- this.pushCircle(positions, colors, head.x, head.y, headRadius * 1.35, outerColor, scene)
- this.pushCircle(positions, colors, head.x, head.y, headRadius, innerColor, scene)
- }
- }
- getTrimmedCourseLeg(
- leg: ProjectedCourseLeg,
- controlRadiusMeters: number,
- scene: MapScene,
- ): { from: { x: number; y: number }; to: { x: number; y: number } } | null {
- return this.trimSegment(
- leg.from,
- leg.to,
- this.getLegTrim(leg.fromKind, controlRadiusMeters, scene),
- this.getLegTrim(leg.toKind, controlRadiusMeters, scene),
- )
- }
- getGuideFlowOuterColor(progress: number): RgbaColor {
- return [0.28, 0.92, 1, 0.14 + progress * 0.22]
- }
- getGuideFlowInnerColor(progress: number): RgbaColor {
- return [0.94, 0.99, 1, 0.38 + progress * 0.42]
- }
- getLegTrim(kind: ProjectedCourseLeg['fromKind'], controlRadiusMeters: number, scene: MapScene): number {
- if (kind === 'start') {
- return this.getMetric(scene, controlRadiusMeters * (1 - START_RING_WIDTH_RATIO / 2))
- }
- if (kind === 'finish') {
- return this.getMetric(scene, controlRadiusMeters * (1 - FINISH_RING_WIDTH_RATIO / 2))
- }
- return this.getMetric(scene, controlRadiusMeters * LEG_TRIM_TO_RING_CENTER_RATIO)
- }
- trimSegment(
- from: { x: number; y: number },
- to: { x: number; y: number },
- fromTrim: number,
- toTrim: number,
- ): { from: { x: number; y: number }; to: { x: number; y: number } } | null {
- const dx = to.x - from.x
- const dy = to.y - from.y
- const length = Math.sqrt(dx * dx + dy * dy)
- if (!length || length <= fromTrim + toTrim) {
- return null
- }
- const ux = dx / length
- const uy = dy / length
- return {
- from: {
- x: from.x + ux * fromTrim,
- y: from.y + uy * fromTrim,
- },
- to: {
- x: to.x - ux * toTrim,
- y: to.y - uy * toTrim,
- },
- }
- }
- pushStartTriangle(
- positions: number[],
- colors: number[],
- centerX: number,
- centerY: number,
- headingDeg: number | null,
- controlRadiusMeters: number,
- color: RgbaColor,
- scene: MapScene,
- ): void {
- const startRadius = this.getMetric(scene, controlRadiusMeters)
- const startRingWidth = this.getMetric(scene, controlRadiusMeters * START_RING_WIDTH_RATIO)
- const headingRad = ((headingDeg === null ? 0 : headingDeg) - 90) * Math.PI / 180
- const vertices = [0, 1, 2].map((index) => {
- const angle = headingRad + index * (Math.PI * 2 / 3)
- return {
- x: centerX + Math.cos(angle) * startRadius,
- y: centerY + Math.sin(angle) * startRadius,
- }
- })
- this.pushSegment(positions, colors, vertices[0], vertices[1], startRingWidth, color, scene)
- this.pushSegment(positions, colors, vertices[1], vertices[2], startRingWidth, color, scene)
- this.pushSegment(positions, colors, vertices[2], vertices[0], startRingWidth, color, scene)
- }
- pushRing(
- positions: number[],
- colors: number[],
- centerX: number,
- centerY: number,
- outerRadius: number,
- innerRadius: number,
- color: RgbaColor,
- scene: MapScene,
- ): void {
- const segments = 36
- for (let index = 0; index < segments; index += 1) {
- const startAngle = index / segments * Math.PI * 2
- const endAngle = (index + 1) / segments * Math.PI * 2
- const outerStart = this.toClip(centerX + Math.cos(startAngle) * outerRadius, centerY + Math.sin(startAngle) * outerRadius, scene)
- const outerEnd = this.toClip(centerX + Math.cos(endAngle) * outerRadius, centerY + Math.sin(endAngle) * outerRadius, scene)
- const innerStart = this.toClip(centerX + Math.cos(startAngle) * innerRadius, centerY + Math.sin(startAngle) * innerRadius, scene)
- const innerEnd = this.toClip(centerX + Math.cos(endAngle) * innerRadius, centerY + Math.sin(endAngle) * innerRadius, scene)
- this.pushTriangle(positions, colors, outerStart, outerEnd, innerStart, color)
- this.pushTriangle(positions, colors, innerStart, outerEnd, innerEnd, color)
- }
- }
- pushSegment(
- positions: number[],
- colors: number[],
- start: { x: number; y: number },
- end: { x: number; y: number },
- width: number,
- color: RgbaColor,
- 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: RgbaColor,
- 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: RgbaColor,
- ): 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,
- }
- }
- }
|