From a96072aef1fcba3b3b59e743fc406afe4a716dab Mon Sep 17 00:00:00 2001 From: sakitam-fdd Date: Mon, 12 Jun 2023 21:14:16 +0800 Subject: [PATCH 1/3] wip(gl-core): add picking --- examples/mapbox-tile.html | 17 +- examples/maptalks-particles-layer.html | 24 +- examples/timeline-pattern.html | 9 +- examples/wind-raster.html | 7 + packages/gl-core/src/renderer/index.ts | 42 +- packages/gl-core/src/renderer/pass/base.ts | 2 +- packages/gl-core/src/renderer/pass/picker.ts | 150 ++++ .../src/shaders/particles/draw.vert.glsl | 2 +- packages/gl-core/src/shaders/picker.frag.glsl | 18 + packages/mapbox-gl/src/layer.ts | 14 + packages/maptalks/src/layer.ts | 15 +- packages/maptalks/typings.d.ts | 3 + packages/particles-poc/src/maptalks.ts | 739 ++++++++++++++++++ 13 files changed, 1006 insertions(+), 36 deletions(-) create mode 100644 packages/gl-core/src/renderer/pass/picker.ts create mode 100644 packages/gl-core/src/shaders/picker.frag.glsl create mode 100644 packages/particles-poc/src/maptalks.ts diff --git a/examples/mapbox-tile.html b/examples/mapbox-tile.html index dfbdf8fb..2fb7b8bf 100644 --- a/examples/mapbox-tile.html +++ b/examples/mapbox-tile.html @@ -92,7 +92,7 @@ const tileSource = new mapboxWind.TileSource('temp', { type: 'tile', tileSize: [256, 256], - url: 'http://localhost:5000/jpeg/{z}/{x}/{y}.jpeg', + url: 'http://localhost:5000/processed/2023040400/2023040409/{z}/{x}/{y}/temp-surface.png', minZoom: 0, maxZoom: 4, // subdomains: ['a', 'b', 'c', 'd'], @@ -102,7 +102,7 @@ const imageSource = new mapboxWind.ImageSource('temp', { type: 'image', - url: 'http://localhost:5000/jpeg/0/0/0.jpeg', + url: 'http://localhost:5000/processed/2023040400/2023040409/0/0/0/temp-surface.png', coordinates: [ [-180, 85.051129], [180, 85.051129], @@ -110,14 +110,14 @@ [-180, -85.051129], ], decodeType: 3, + dataRange: [-60, 60] }); map.on('click', () => { - imageSource.setUrl('http://localhost:5000/jpeg/1/0/0.jpeg'); + // imageSource.setUrl('http://localhost:5000/jpeg/1/0/0.jpeg'); }); const fillLayer = new mapboxWind.Layer('temp', imageSource, { - wrapX: true, wireframe: true, styleSpec: { 'fill-color': [ @@ -141,10 +141,17 @@ heightSegments: 1, displayRange: [-80, 80], hasMask: false, - renderPasses: ['ColorizeComposePass', 'ColorizePass'], + picking: true, + renderType: 1, }); map.addLayer(fillLayer); + + map.on('click', e => { + fillLayer.picker(e.lngLat).then((v) => { + console.log(v) + }) + }); // map.addLayer(fillLayer1); }); diff --git a/examples/maptalks-particles-layer.html b/examples/maptalks-particles-layer.html index 672e3a9c..2bf03a13 100644 --- a/examples/maptalks-particles-layer.html +++ b/examples/maptalks-particles-layer.html @@ -41,28 +41,21 @@ mtkWind.configDeps(['https://unpkg.com/geotiff/dist-browser/geotiff.js', 'https://unpkg.com/exifr/dist/full.umd.js']); - const source = new mtkWind.TimelineSource('temp', { - sourceType: 'ImageSource', + const source = new mtkWind.ImageSource('temp', { + type: 'image', + tileSize: 256, + // minZoom: 0, + // maxZoom: 0, + // roundZoom: false, + decodeType: 3, coordinates: [ [-180, 85.051129], [180, 85.051129], [180, -85.051129], [-180, -85.051129], ], - // sourceType: 'TileSource', - // tileSize: 256, - // minZoom: 0, - // maxZoom: 2, - // roundZoom: true, - duration: 0.1, - endDelay: 0, - repeat: true, - autoplay: false, - decodeType: 3, - intervals: Array.from({ length: 23 }).map((i, index) => ({ - url: `http://localhost:5000/processed/2023041700/20230417${index < 10 ? '0' + index.toString() : index.toString()}/0/0/0/wind-surface.jpeg` - })), wrapX: true, + url: 'http://localhost:5000/processed/2023042600/2023042601/0/0/0/wind-surface.jpeg', }); window.source = source; @@ -136,6 +129,7 @@ displayRange: [0, 104], hasMask: false, renderType: 2, + picking: true }); map.addLayer(layer); diff --git a/examples/timeline-pattern.html b/examples/timeline-pattern.html index fa72c971..451199fb 100644 --- a/examples/timeline-pattern.html +++ b/examples/timeline-pattern.html @@ -116,7 +116,7 @@ duration: 0.1, endDelay: 0, repeat: true, - autoplay: true, + autoplay: false, decodeType: 3, intervals: Array.from({ length: 23 }).map((i, index) => ({ url: `http://localhost:5000/processed/2023041700/20230417${index < 10 ? '0' + index.toString() : index.toString()}/{z}/{x}/{y}/wind-surface.jpeg` @@ -151,9 +151,16 @@ displayRange: [0, 104], hasMask: false, renderType: 1, + picking: true, }); map.addLayer(layer); + + map.on('click', e => { + layer.picker(e.lngLat).then((v) => { + console.log(v) + }) + }); }); diff --git a/examples/wind-raster.html b/examples/wind-raster.html index 66a4dee9..6662bc2e 100644 --- a/examples/wind-raster.html +++ b/examples/wind-raster.html @@ -115,9 +115,16 @@ ], }, renderType: 0, + picking: true, }); map.addLayer(layer); + + map.on('click', e => { + layer.picker(e.lngLat).then((v) => { + console.log(v) + }) + }) }); diff --git a/packages/gl-core/src/renderer/index.ts b/packages/gl-core/src/renderer/index.ts index f967170b..b8bef97f 100644 --- a/packages/gl-core/src/renderer/index.ts +++ b/packages/gl-core/src/renderer/index.ts @@ -1,4 +1,4 @@ -import { DataTexture, Raf, Renderer, Scene, utils, Vector2 } from '@sakitam-gis/vis-engine'; +import {DataTexture, Raf, Renderer, Scene, utils, Vector2} from '@sakitam-gis/vis-engine'; import wgw from 'wind-gl-worker'; import Pipelines from './Pipelines'; import ColorizeComposePass from './pass/color/compose'; @@ -9,10 +9,11 @@ import ParticlesComposePass from './pass/particles/compose'; import UpdatePass from './pass/particles/update'; import ScreenPass from './pass/particles/screen'; import ParticlesPass from './pass/particles/particles'; -import { isFunction, resolveURL } from '../utils/common'; -import { createLinearGradient, createZoom } from '../utils/style-parser'; -import { getBandType, RenderFrom, RenderType } from '../type'; -import { SourceType } from '../source'; +import PickerPass from './pass/picker'; +import {isFunction, resolveURL} from '../utils/common'; +import {createLinearGradient, createZoom} from '../utils/style-parser'; +import {getBandType, RenderFrom, RenderType} from '../type'; +import {SourceType} from '../source'; import Tile from '../tile/Tile'; export interface LayerOptions { @@ -50,6 +51,10 @@ export interface LayerOptions { widthSegments?: number; heightSegments?: number; wireframe?: boolean; + /** + * 是否开启拾取 + */ + picking?: boolean; /** * 可以为任意 GeoJSON 数据 */ @@ -215,6 +220,15 @@ export default class Layer { textureNext: composePass.textures.next, }); this.renderPipeline?.addPass(composePass); + if (this.options.picking) { + const pickerPass = new PickerPass('PickerPass', this.renderer, { + source: this.source, + texture: composePass.textures.current, + textureNext: composePass.textures.next, + useFloatTexture: false, + }); + this.renderPipeline?.addPass(pickerPass); + } this.renderPipeline?.addPass(rasterPass); } else if (this.options.renderType === RenderType.colorize) { const composePass = new ColorizeComposePass('ColorizeComposePass', this.renderer, { @@ -230,6 +244,17 @@ export default class Layer { textureNext: composePass.textures.next, }); this.renderPipeline?.addPass(composePass); + + if (this.options.picking) { + const pickerPass = new PickerPass('PickerPass', this.renderer, { + source: this.source, + texture: composePass.textures.current, + textureNext: composePass.textures.next, + useFloatTexture: true, + }); + this.renderPipeline?.addPass(pickerPass); + } + this.renderPipeline?.addPass(colorizePass); } else if (this.options.renderType === RenderType.particles) { const composePass = new ParticlesComposePass('ParticlesComposePass', this.renderer, { @@ -533,6 +558,13 @@ export default class Layer { } } + async picker(pixel = [0, 0]) { + if (!this.renderPipeline) return null; + const pickerPass = this.renderPipeline.getPass('PickerPass'); + if (!pickerPass) return null; + return pickerPass.render(undefined, undefined, pixel); + } + prerender(cameras) { if (this.renderPipeline) { this.renderPipeline.prerender( diff --git a/packages/gl-core/src/renderer/pass/base.ts b/packages/gl-core/src/renderer/pass/base.ts index f2f2f6ac..54b57ad1 100644 --- a/packages/gl-core/src/renderer/pass/base.ts +++ b/packages/gl-core/src/renderer/pass/base.ts @@ -25,7 +25,7 @@ export default class Pass { this.#enabled = state; } - render(rendererParams, rendererState) { + render(rendererParams, rendererState, cb) { throw new Error(ERR_PASS_METHOD_UNDEFINED); } diff --git a/packages/gl-core/src/renderer/pass/picker.ts b/packages/gl-core/src/renderer/pass/picker.ts new file mode 100644 index 00000000..f07d42ff --- /dev/null +++ b/packages/gl-core/src/renderer/pass/picker.ts @@ -0,0 +1,150 @@ +import { Program, Renderer, Mesh, Geometry, Texture, RenderTarget } from '@sakitam-gis/vis-engine'; +import Pass from './base'; +import vert from '../../shaders/common.vert.glsl'; +import frag from '../../shaders/picker.frag.glsl'; +import * as shaderLib from '../../shaders/shaderLib'; +import { SourceType } from '../../source'; + +export interface PickerPassOptions { + source: SourceType; + texture: Texture; + textureNext: Texture; + useFloatTexture?: boolean; +} + +/** + * picking + */ +export default class PickerPass extends Pass { + readonly #program: Program; + readonly #mesh: Mesh; + readonly #geometry: Geometry; + readonly prerender = true; + + #picker: RenderTarget; + + #rendererParams: any; + #rendererState: any; + + constructor( + id: string, + renderer: Renderer, + options: PickerPassOptions = {} as PickerPassOptions, + ) { + super(id, renderer, options); + + this.#program = new Program(renderer, { + vertexShader: vert, + fragmentShader: frag, + uniforms: { + u_fade_t: { + value: 0, + }, + u_texture: { + value: this.options.texture, + }, + u_textureNext: { + value: this.options.textureNext, + }, + }, + includes: shaderLib, + transparent: true, + }); + + this.#geometry = new Geometry(renderer, { + position: { + size: 2, + data: new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), + }, + uv: { + size: 2, + data: new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), + }, + index: { + size: 1, + data: new Uint16Array([0, 1, 2, 2, 1, 3]), + }, + }); + + this.#mesh = new Mesh(renderer, { + mode: renderer.gl.TRIANGLES, + program: this.#program, + geometry: this.#geometry, + }); + + // @link https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html + const opt = { + width: this.renderer.width, + height: this.renderer.height, + minFilter: renderer.gl.NEAREST, + magFilter: renderer.gl.NEAREST, + type: this.renderer.gl.UNSIGNED_BYTE, + format: this.renderer.gl.RGBA, + generateMipmaps: true, + internalFormat: this.renderer.gl.RGBA, + stencil: false, + }; + + if (options.useFloatTexture) { + opt.type = this.renderer.gl.FLOAT; + opt.internalFormat = this.renderer.isWebGL2 + ? (this.renderer.gl as WebGL2RenderingContext).RGBA32F + : this.renderer.gl.RGBA; + } + + this.#picker = new RenderTarget(renderer, { + ...opt, + name: 'pickerRenderTargetTexture', + }); + } + + resize(width: number, height: number) { + this.#picker.resize(width, height); + } + + /** + * @param rendererParams + * @param rendererState + * @param pixel + */ + render(rendererParams = this.#rendererParams, rendererState = this.#rendererState, pixel) { + return new Promise((resolve) => { + this.#rendererParams = + this.#rendererParams !== rendererParams ? rendererParams : this.#rendererParams; + this.#rendererState = + this.#rendererState !== rendererState ? rendererState : this.#rendererState; + const camera = rendererParams.cameras.planeCamera; + this.#picker.clear(); + this.#picker.bind(); + this.renderer.setViewport(this.#picker.width, this.#picker.height); + if (rendererState) { + const fade = this.options.source?.getFadeTime?.() || 0; + + this.#mesh.program.setUniform('u_fade_t', fade); + + this.#mesh.updateMatrix(); + this.#mesh.worldMatrixNeedsUpdate = false; + this.#mesh.worldMatrix.multiply(camera.worldMatrix, this.#mesh.localMatrix); + this.#mesh.draw({ + ...rendererParams, + camera, + }); + + if (pixel) { + const a = this.options.useFloatTexture ? new Float32Array(4) : new Uint8Array(4); + this.renderer.gl.readPixels( + pixel[0], + pixel[1], + 1.0, + 1.0, + this.renderer.gl.RGBA, + this.options.useFloatTexture ? this.renderer.gl.FLOAT : this.renderer.gl.UNSIGNED_BYTE, + a, + ); + resolve(a); + } + } + this.#picker.unbind(); + }); + } +} diff --git a/packages/gl-core/src/shaders/particles/draw.vert.glsl b/packages/gl-core/src/shaders/particles/draw.vert.glsl index 1f257453..e1cc75f3 100644 --- a/packages/gl-core/src/shaders/particles/draw.vert.glsl +++ b/packages/gl-core/src/shaders/particles/draw.vert.glsl @@ -78,6 +78,6 @@ void main() { gl_PointSize = u_particleSize; - gl_Position = projectionMatrix * modelViewMatrix * vec4(v_current_particle_pos, 0.0, 1.0); + gl_Position = projectionMatrix * modelViewMatrix * vec4(v_current_particle_pos * vec2(262144.0 * 2.0, 262144.0 * 2.0) + vec2(-262144.0, -262144.0), 0.0, 1.0); } diff --git a/packages/gl-core/src/shaders/picker.frag.glsl b/packages/gl-core/src/shaders/picker.frag.glsl new file mode 100644 index 00000000..1560a28f --- /dev/null +++ b/packages/gl-core/src/shaders/picker.frag.glsl @@ -0,0 +1,18 @@ +precision highp float; + +uniform sampler2D u_texture; +uniform sampler2D u_textureNext; +uniform float u_fade_t; + +varying vec2 vUv; + +void main () { + vec2 uv = vUv; + + vec4 color0 = texture2D(u_texture, vUv); + vec4 color1 = texture2D(u_textureNext, vUv); + + vec4 color = mix(color0, color1, u_fade_t); + + gl_FragColor = color; +} diff --git a/packages/mapbox-gl/src/layer.ts b/packages/mapbox-gl/src/layer.ts index 34b02a7c..208ad534 100644 --- a/packages/mapbox-gl/src/layer.ts +++ b/packages/mapbox-gl/src/layer.ts @@ -155,6 +155,7 @@ export default class Layer { widthSegments: this.options.widthSegments, heightSegments: this.options.heightSegments, wireframe: this.options.wireframe, + picking: this.options.picking, getZoom: () => this.map?.getZoom() as number, triggerRepaint: () => { this.map?.triggerRepaint(); @@ -325,6 +326,19 @@ export default class Layer { }); } + async picker(coordinates) { + if (!this.options.picking) { + console.warn('[Layer]: please enable picking options!'); + return null; + } + if (!this.layer || !coordinates || !this.map) { + console.warn('[Layer]: layer not initialized!'); + return null; + } + const point = this.map.project(coordinates); + return this.layer.picker([point.x, point.y]); + } + render() { this.scene.worldMatrixNeedsUpdate = true; this.scene.updateMatrixWorld(); diff --git a/packages/maptalks/src/layer.ts b/packages/maptalks/src/layer.ts index 8d41b19e..fd9a2c61 100644 --- a/packages/maptalks/src/layer.ts +++ b/packages/maptalks/src/layer.ts @@ -12,8 +12,6 @@ import { import { Layer as LayerCore, SourceType, TileID } from 'wind-gl-core'; -type WithNull = T | null; - highPrecision(true); function getGLRes(map) { @@ -51,7 +49,10 @@ function getTileBounds(map, x, y, z, wrap = 0) { const res = getGLRes(map); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // @ts-ignore const p1 = coordinateToPoint(map, new maptalks.Coordinate([min[0], max[1]]), res); // 左上 + // @ts-ignore const p2 = coordinateToPoint(map, new maptalks.Coordinate([max[0], min[1]]), res); // 右下 // const projObject = map.getProjection().fullExtent; @@ -80,10 +81,10 @@ function getTileBounds(map, x, y, z, wrap = 0) { // ); return { - left: p1.x + wrap, - top: p1.y, - right: p2.x + wrap, - bottom: p2.y, + left: 0 + wrap, + top: 0, + right: 1 + wrap, + bottom: 1, lngLatBounds: [min[0], min[1], max[0], max[1]], }; } @@ -301,8 +302,6 @@ const options: BaseLayerOptionType = { }; class Layer extends maptalks.TileLayer { - options: any; - map: any; type: string; _needsUpdate = true; _coordCache = {}; diff --git a/packages/maptalks/typings.d.ts b/packages/maptalks/typings.d.ts index 4896bbfe..6b32f447 100644 --- a/packages/maptalks/typings.d.ts +++ b/packages/maptalks/typings.d.ts @@ -1,2 +1,5 @@ declare module '*.json'; declare module 'maptalks'; + +type WithNull = T | null; +type WithUndef = T | undefined; diff --git a/packages/particles-poc/src/maptalks.ts b/packages/particles-poc/src/maptalks.ts new file mode 100644 index 00000000..195da0c0 --- /dev/null +++ b/packages/particles-poc/src/maptalks.ts @@ -0,0 +1,739 @@ +import REGL, { DrawCommand, Framebuffer2D, Regl, Texture2D } from 'regl'; +import mapboxgl from 'mapbox-gl'; +import particleUpdateVert from './shaders/particle-update.vert.glsl'; +import particleUpdateFrag from './shaders/particle-update.frag.glsl'; +import particleDrawVert from './shaders/particle-draw.vert.glsl'; +import globalParticleDrawVert from './shaders/global-particle-draw.vert.glsl'; +import particleDrawFrag from './shaders/particle-draw.frag.glsl'; +import screenVert from './shaders/screen.vert.glsl'; +import screenFrag from './shaders/screen.frag.glsl'; + +function mod(x, y) { + return ((x % y) + y) % y; +} + +/* eslint-disable @typescript-eslint/no-namespace */ +declare namespace UpdateCommand { + export interface Uniforms { + u_wind: Texture2D; + u_particles: Texture2D; + u_rand_seed: number; + u_drop_rate: number; + u_speed_factor: number; + u_drop_rate_bump: number; + u_wind_res: REGL.Vec2; + u_wind_min: REGL.Vec2; + u_wind_max: REGL.Vec2; + u_bbox: REGL.Vec4; + u_data_bbox: REGL.Vec4; + u_initialize: boolean; + } + + export interface Attributes { + a_pos: Float32Array; + } + + export type Props = Omit; +} + +declare namespace DrawParticlesCommand { + export interface Uniforms { + u_wind: Texture2D; + u_particles: Texture2D; + u_particles_next: Texture2D; + u_color_ramp: Texture2D; + u_particles_res: REGL.Vec2; + u_dateline_offset: number; + u_wind_min: REGL.Vec2; + u_wind_max: REGL.Vec2; + u_matrix: REGL.Mat4; + u_bbox: REGL.Vec4; + u_data_bbox: REGL.Vec4; + u_resolution: REGL.Vec2; + u_aspectRatio: number; + } + + export interface Attributes { + a_index: Float32Array; + } + + export type Props = Omit & { + count: number; + }; +} + +declare namespace GlobalDrawParticlesCommand { + export interface Uniforms { + u_wind: Texture2D; + u_particles: Texture2D; + u_particles_next: Texture2D; + u_color_ramp: Texture2D; + u_particles_res: REGL.Vec2; + u_dateline_offset: number; + u_wind_min: REGL.Vec2; + u_wind_max: REGL.Vec2; + u_bbox: REGL.Vec4; + u_data_bbox: REGL.Vec4; + u_resolution: REGL.Vec2; + u_aspectRatio: number; + + u_matrix: REGL.Mat4; + u_globeToMercMatrix: REGL.Mat4; + u_globeToMercatorTransition: number; + u_centerInMerc: REGL.Vec2; + u_pixelsPerMeterRatio: number; + } + + export interface Attributes { + a_index: Float32Array; + } + + export type Props = Omit & { + count: number; + }; +} + +declare namespace TextureCommand { + export interface Uniforms { + u_screen: Texture2D; + u_opacity: number; + u_fade: number; + } + + export interface Attributes { + a_pos: Float32Array; + } + + export type Props = Omit & { + blendEnable: REGL.DynamicVariable; + }; +} + +interface ParticlesLayerOptions { + speedFactor: number; + numParticles: number; + dropRate: number; + dropRateBump: number; + fadeOpacity: number; +} + +export default class ParticlesLayer { + public id: string; + public type = 'custom'; + public renderingMode: '2d' | '3d' = '2d'; + public options: ParticlesLayerOptions; + public map: any; + public windData: any; + private pointing: boolean; + + private particleStateResolution: number; + private indexCount: number; + private regl: Regl; + private particleStateTexture0: Texture2D; + private particleStateTexture1: Texture2D; + private windTexture: Texture2D; + private backgroundTexture: Texture2D; + private screenTexture: Texture2D; + private particleIndexBuffer: Float32Array; + private quadBuffer: Float32Array; + private backgroundBuffer: Float32Array; + private gl: WithUndef; + private initialized: boolean; + private updatefbo: Framebuffer2D; + private commonfbo: Framebuffer2D; + private updateCommand: DrawCommand; + private drawCommand: DrawCommand; + private globalDrawCommand: DrawCommand; + private textureCommand: DrawCommand; + + #numParticles: number; + + constructor(id: string, options: Partial = {}) { + this.id = id; + this.options = { + speedFactor: 0.5, + fadeOpacity: 0.93, + dropRate: 0.003, + dropRateBump: 0.002, + numParticles: 65535, + ...options, + }; + + this.pointing = false; + } + + public updateOptions(options: Partial) { + this.options = { + ...this.options, + ...options, + }; + + this.resize(); + } + + initializeParticles(count) { + const particleRes = (this.particleStateResolution = Math.ceil(Math.sqrt(count))); + this.#numParticles = particleRes * particleRes; + + this.indexCount = this.#numParticles * 1; + + const particleState = new Float32Array(this.#numParticles * 4); + for (let i = 0; i < particleState.length; i++) { + particleState[i] = Math.floor(Math.random()); // randomize the initial particle positions + } + this.particleStateTexture0 = this.regl.texture({ + width: particleRes, + height: particleRes, + data: particleState, + type: 'float', + }); + this.particleStateTexture1 = this.regl.texture({ + width: particleRes, + height: particleRes, + data: particleState, + type: 'float', + }); + + const particleIndices = new Float32Array(this.indexCount); + for (let i = 0; i < this.indexCount; i++) { + particleIndices[i] = i; + } + this.particleIndexBuffer = particleIndices; + } + + setWind(windData) { + return new Promise(() => { + const image = new Image(); + image.onload = () => { + this.windData = Object.assign({}, windData, { + wind: image, + }); + if (this.map) { + this.initialize(); + this.map.triggerRepaint(); + } + }; + image.src = windData.url; + }); + } + + onAdd(map, gl: WebGLRenderingContext) { + this.gl = gl; + this.regl = REGL({ + gl: this.gl, + extensions: ['OES_texture_float', 'OES_element_index_uint'], + attributes: { + antialias: true, + preserveDrawingBuffer: false, + }, + }); + this.map = map; + if (this.windData) { + this.initialize(); + } + } + + initialize() { + this.updatefbo = this.regl.framebuffer({ + colorType: 'float', + }); + this.commonfbo = this.regl.framebuffer(); + this.initialized = true; + + this.resize(); + + const size = this.getSize(); + + this.quadBuffer = new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]); + this.backgroundBuffer = new Float32Array([0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]); + + this.updateCommand = this.regl< + UpdateCommand.Uniforms, + UpdateCommand.Attributes, + UpdateCommand.Props + >({ + frag: particleUpdateFrag, + vert: particleUpdateVert, + attributes: { + a_pos: this.quadBuffer, + }, + + uniforms: { + u_wind: this.regl.prop('u_wind'), + u_particles: this.regl.prop('u_particles'), + u_rand_seed: this.regl.prop('u_rand_seed'), + u_wind_res: this.regl.prop('u_wind_res'), + u_drop_rate: this.regl.prop('u_drop_rate'), + u_speed_factor: this.regl.prop('u_speed_factor'), + u_drop_rate_bump: this.regl.prop( + 'u_drop_rate_bump', + ), + u_wind_min: this.regl.prop('u_wind_min'), + u_wind_max: this.regl.prop('u_wind_max'), + u_bbox: this.regl.prop('u_bbox'), + u_data_bbox: this.regl.prop('u_data_bbox'), + u_initialize: this.regl.prop('u_initialize'), + }, + + viewport: () => ({ + width: this.particleStateResolution, + height: this.particleStateResolution, + }), + + depth: { + enable: false, + mask: true, + func: 'less', + range: [0, 1], + }, + + blend: { + enable: false, + func: { + src: 'src alpha', + dst: 'one minus src alpha', + }, + color: [0, 0, 0, 0], + }, + + count: 6, + primitive: 'triangles', + }); + this.drawCommand = this.regl< + DrawParticlesCommand.Uniforms, + DrawParticlesCommand.Attributes, + DrawParticlesCommand.Props + >({ + frag: particleDrawFrag, + vert: particleDrawVert, + attributes: { + a_index: this.particleIndexBuffer, + }, + + uniforms: { + u_wind: this.regl.prop('u_wind'), + u_particles: this.regl.prop('u_particles'), + u_particles_next: this.regl.prop( + 'u_particles_next', + ), + u_color_ramp: this.regl.prop('u_color_ramp'), + u_particles_res: this.regl.prop( + 'u_particles_res', + ), + u_dateline_offset: this.regl.prop( + 'u_dateline_offset', + ), + u_wind_min: this.regl.prop('u_wind_min'), + u_wind_max: this.regl.prop('u_wind_max'), + u_matrix: (_, { u_matrix }) => u_matrix, + u_bbox: this.regl.prop('u_bbox'), + u_data_bbox: this.regl.prop('u_data_bbox'), + u_resolution: size as REGL.Vec2, + u_aspectRatio: size[0] / size[1], + }, + + viewport: () => { + const sz = this.getSize(); + return { + width: sz[0], + height: sz[1], + }; + }, + + depth: { + enable: false, + mask: true, + func: 'less', + range: [0, 1], + }, + + blend: { + enable: true, + func: { + src: 'src alpha', + dst: 'one minus src alpha', + }, + color: [0, 0, 0, 0], + }, + + count: this.regl.prop('count'), + primitive: 'triangles', + // primitive: "points" + }); + + this.globalDrawCommand = this.regl< + GlobalDrawParticlesCommand.Uniforms, + GlobalDrawParticlesCommand.Attributes, + GlobalDrawParticlesCommand.Props + >({ + frag: particleDrawFrag, + vert: globalParticleDrawVert, + attributes: { + a_index: this.particleIndexBuffer, + }, + + uniforms: { + u_wind: this.regl.prop('u_wind'), + u_particles: this.regl.prop('u_particles'), + u_particles_next: this.regl.prop( + 'u_particles_next', + ), + u_color_ramp: this.regl.prop('u_color_ramp'), + u_particles_res: this.regl.prop( + 'u_particles_res', + ), + u_dateline_offset: this.regl.prop( + 'u_dateline_offset', + ), + u_wind_min: this.regl.prop('u_wind_min'), + u_wind_max: this.regl.prop('u_wind_max'), + u_matrix: (_, { u_matrix }) => u_matrix, + u_globeToMercMatrix: (_, { u_globeToMercMatrix }) => u_globeToMercMatrix, + u_globeToMercatorTransition: (_, { u_globeToMercatorTransition }) => u_globeToMercatorTransition, + u_centerInMerc: (_, { u_centerInMerc }) => u_centerInMerc, + u_pixelsPerMeterRatio: (_, { u_pixelsPerMeterRatio }) => u_pixelsPerMeterRatio, + u_bbox: this.regl.prop('u_bbox'), + u_data_bbox: this.regl.prop('u_data_bbox'), + u_resolution: size as REGL.Vec2, + u_aspectRatio: size[0] / size[1], + }, + + viewport: () => { + const sz = this.getSize(); + return { + width: sz[0], + height: sz[1], + }; + }, + + depth: { + enable: false, + mask: true, + func: 'less', + range: [0, 1], + }, + + blend: { + enable: true, + func: { + src: 'src alpha', + dst: 'one minus src alpha', + }, + color: [0, 0, 0, 0], + }, + + count: this.regl.prop('count'), + // primitive: 'triangles', + primitive: "points" + }); + + this.textureCommand = this.regl< + TextureCommand.Uniforms, + TextureCommand.Attributes, + TextureCommand.Props + >({ + frag: screenFrag, + vert: screenVert, + attributes: { + a_pos: this.backgroundBuffer, + }, + + uniforms: { + u_screen: this.regl.prop('u_screen'), + u_opacity: this.regl.prop('u_opacity'), + u_fade: this.regl.prop('u_fade'), + }, + + depth: { + enable: false, + mask: true, + func: 'less', + range: [0, 1], + }, + + blend: { + // @ts-ignore + enable: this.regl.prop('blendEnable'), + func: { + src: 'src alpha', + dst: 'one minus src alpha', + }, + color: [0, 0, 0, 1], + }, + + viewport: () => { + const sz = this.getSize(); + return { + width: sz[0], + height: sz[1], + }; + }, + + count: 4, + primitive: 'triangle strip', + }); + this.windTexture = this.regl.texture(this.windData.wind); + this.map.on('zoom', this.zoom.bind(this)); + this.map.on('movestart', this.moveStart.bind(this)); + this.map.on('moveend', this.moveEnd.bind(this)); + this.map.on('resize', this.resize.bind(this)); + } + + moveStart() { + this.pointing = true; + this.regl.clear({ + color: [0, 0, 0, 0], + depth: 1, + stencil: 0, + framebuffer: this.commonfbo, + }); + // this.reset(true); + } + + moveEnd() { + this.pointing = false; + } + + getExtent() { + const bounds = this.map.getBounds().toArray(); + const xmin = bounds[0][0]; + const ymin = bounds[0][1]; + const xmax = bounds[1][0]; + const ymax = bounds[1][1]; + + const dx = xmax - xmin; + + const minLng = dx < 360 ? mod(xmin + 180, 360) - 180 : -180; + let maxLng = 180; + if (dx < 360) { + maxLng = mod(xmax + 180, 360) - 180; + if (maxLng < minLng) { + maxLng += 360; + } + } + const minLat = Math.max(ymin, this.map.transform.minLat); + const maxLat = Math.min(ymax, this.map.transform.maxLat); + + const mapBounds = [minLng, minLat, maxLng, maxLat]; + + const p0 = mapboxgl.MercatorCoordinate.fromLngLat( + new mapboxgl.LngLat(mapBounds[0], mapBounds[3]), + ); + const p1 = mapboxgl.MercatorCoordinate.fromLngLat( + new mapboxgl.LngLat(mapBounds[2], mapBounds[1]), + ); + + return [p0.x, p0.y, p1.x, p1.y]; + } + + getSize(): number[] { + return [this.map.transform.width, this.map.transform.height]; + } + + zoom() { + console.log('zoom'); + } + + reset(flag?: boolean) { + const size = this.getSize(); + const emptyPixels = new Uint8Array(size[0] * size[1] * 4); + if (!this.backgroundTexture || flag) { + this.backgroundTexture = this.regl.texture({ + width: size[0], + height: size[1], + data: emptyPixels, + }); + } else { + this.backgroundTexture({ + width: size[0], + height: size[1], + data: emptyPixels, + }); + } + if (!this.screenTexture || flag) { + this.screenTexture = this.regl.texture({ + width: size[0], + height: size[1], + data: emptyPixels, + }); + } else { + this.screenTexture({ + width: size[0], + height: size[1], + data: emptyPixels, + }); + } + } + + resize() { + this.initializeParticles(this.options.numParticles); + const size = this.getSize(); + this.reset(); + if (this.commonfbo) { + this.commonfbo({ + width: size[0], + height: size[1], + color: this.screenTexture, + }); + } + } + + onRemove(map) { + this.gl = undefined; + delete this.map; + map.off('zoom', this.zoom); + } + + update() { + this.updatefbo({ + width: this.particleStateResolution, + height: this.particleStateResolution, + color: this.particleStateTexture1, + }); + + this.updatefbo.use(() => { + this.updateCommand({ + u_wind: this.windTexture, + u_particles: this.particleStateTexture0, + u_rand_seed: Math.random(), + u_wind_res: [this.windData.width, this.windData.height], + u_speed_factor: [this.options.speedFactor, this.options.speedFactor], + u_drop_rate: this.options.dropRate, + u_drop_rate_bump: this.options.dropRateBump, + u_wind_min: [this.windData.uMin, this.windData.vMin], + u_wind_max: [this.windData.uMax, this.windData.vMax], + u_bbox: this.getExtent(), + u_data_bbox: this.windData.bbox, + u_initialize: this.initialized, + }); + }); + + this.initialized = false; + + [this.particleStateTexture0, this.particleStateTexture1] = [ + this.particleStateTexture1, + this.particleStateTexture0, + ]; + } + + drawTexture(matrix, dateLineOffset, isGlobal, params) { + const size = this.getSize(); + this.commonfbo({ + width: size[0], + height: size[1], + color: this.screenTexture, + }); + + this.commonfbo.use(() => { + this.textureCommand({ + u_screen: this.backgroundTexture, + u_opacity: this.options.fadeOpacity || 0.97, + u_fade: 1, + blendEnable: false, + }); + + this.drawParticles(matrix, dateLineOffset, isGlobal, params); + }); + + [this.backgroundTexture, this.screenTexture] = [this.screenTexture, this.backgroundTexture]; + } + + drawScreen() { + this.textureCommand({ + u_screen: this.screenTexture, + u_opacity: this.options.fadeOpacity || 0.95, + u_fade: 1, + blendEnable: true, + }); + } + + drawParticles(matrix, dateLineOffset, isGlobal, params) { + if (isGlobal) { + this.globalDrawCommand({ + u_wind: this.windTexture, + u_particles: this.particleStateTexture0, + u_particles_next: this.particleStateTexture1, + u_particles_res: this.particleStateResolution, + u_dateline_offset: dateLineOffset, + u_wind_min: [this.windData.uMin, this.windData.vMin], + u_wind_max: [this.windData.uMax, this.windData.vMax], + u_matrix: matrix, + count: this.indexCount, + u_bbox: this.getExtent(), + u_data_bbox: this.windData.bbox, + u_globeToMercMatrix: params.globeToMercMatrix, + u_globeToMercatorTransition: params.transition, + u_centerInMerc: params.centerInMercator, + u_pixelsPerMeterRatio: params.pixelsPerMeterRatio, + }); + } else { + this.drawCommand({ + u_wind: this.windTexture, + u_particles: this.particleStateTexture0, + u_particles_next: this.particleStateTexture1, + u_particles_res: this.particleStateResolution, + u_dateline_offset: dateLineOffset, + u_wind_min: [this.windData.uMin, this.windData.vMin], + u_wind_max: [this.windData.uMax, this.windData.vMax], + u_matrix: matrix, + count: this.indexCount, + u_bbox: this.getExtent(), + u_data_bbox: this.windData.bbox, + }); + } + + this.map.triggerRepaint(); + } + + // shouldRerenderTiles() { + // // return true only when frame content has changed otherwise, all the terrain + // // render cache would be invalidated and redrawn causing huge drop in performance. + // return false; + // } + + prerender( + gl, + projectionMatrix, + projection, + globeToMercMatrix, + transition, + centerInMercator, + pixelsPerMeterRatio, + ) { + if (this.windData) { + this.regl._refresh(); + this.update(); + if (!this.pointing) { + this.drawTexture(projectionMatrix, 0, projection && projection.name === 'globe', { + globeToMercMatrix, + transition, + centerInMercator, + pixelsPerMeterRatio, + }); + } + } + } + + render( + gl, + projectionMatrix, + projection, + globeToMercMatrix, + transition, + centerInMercator, + pixelsPerMeterRatio, + ) { + if (this.windData) { + if (!this.pointing) { + this.drawScreen(); + } else { + this.drawParticles(projectionMatrix, 0, projection && projection.name === 'globe', { + globeToMercMatrix, + transition, + centerInMercator, + pixelsPerMeterRatio, + }); + } + } + } +} From 6b31cc8f691f76474039909c8d2a78c89cce8775 Mon Sep 17 00:00:00 2001 From: liuyafei Date: Thu, 14 Dec 2023 11:05:45 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E9=AB=98=E5=BE=B7=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?api2.0=E7=89=88=E6=9C=AC=E5=8D=87=E7=BA=A7=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/amap.html | 2 +- packages/amap/src/index.ts | 41 +++++++++++++++----------------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/examples/amap.html b/examples/amap.html index 2f6b9e27..e9baef90 100644 --- a/examples/amap.html +++ b/examples/amap.html @@ -6,7 +6,7 @@ - +