This commit is contained in:
2912401452 2021-12-07 17:33:53 +08:00
commit 045986782e
22 changed files with 1701 additions and 19 deletions

View File

@ -17,6 +17,7 @@ import styleMapping from '../../shaders/styleMapping.glsl';
import styleMappingCalOpacity from '../../shaders/styleMappingCalOpacity.glsl';
import styleMappingCalStrokeOpacity from '../../shaders/styleMappingCalStrokeOpacity.glsl';
import styleMappingCalStrokeWidth from '../../shaders/styleMappingCalStrokeWidth.glsl';
import styleMappingCalThetaOffset from '../../shaders/styleMappingCalThetaOffset.glsl';
const precisionRegExp = /precision\s+(high|low|medium)p\s+float/;
const globalDefaultprecision =
@ -39,6 +40,10 @@ export default class ShaderModuleService implements IShaderModuleService {
this.registerModule('light', { vs: light, fs: '' });
this.registerModule('picking', { vs: pickingVert, fs: pickingFrag });
this.registerModule('styleMapping', { vs: styleMapping, fs: '' });
this.registerModule('styleMappingCalThetaOffset', {
vs: styleMappingCalThetaOffset,
fs: '',
});
this.registerModule('styleMappingCalOpacity', {
vs: styleMappingCalOpacity,
fs: '',

View File

@ -23,6 +23,10 @@ bool hasOffsets() { // 判断 cell 中是否存在 offsets 的数据
return u_cellTypeLayout[2][0] > 0.0 && u_cellTypeLayout[3][3] > 0.0;
}
bool hasThetaOffset() { // 判断 cell 中是否存在 thetaOffset 的数据
return u_cellTypeLayout[2][1] > 0.0 && u_cellTypeLayout[3][3] > 0.0;
}
// 根据坐标位置先是计算 uv ,然后根据 uv 从数据纹理中取值
float pos2value(vec2 pos, float columnWidth, float rowHeight) {
float u = (pos.r - 1.0) * columnWidth + columnWidth/2.0;
@ -53,12 +57,13 @@ float calCellCount() {
// u_cellTypeLayout
// cal_height, WIDTH, 0.0, 0.0, // rowCount columnCount - 几行几列
// 1.0, 1.0, 1.0, 0.0, // opacity strokeOpacity strokeWidth stroke - 1.0 表示有数据映射、0.0 表示没有
// 1.0, 0.0, 0.0, 0.0, // offsets
// 1.0, 1.0, 0.0, 0.0, // offsets thetaOffset
// 0.0, 0.0, 0.0, 0.0
return u_cellTypeLayout[1][0] + // opacity
u_cellTypeLayout[1][1] + // strokeOpacity
u_cellTypeLayout[1][2] + // strokeWidth
u_cellTypeLayout[1][3] * 4.0 + // stroke
u_cellTypeLayout[2][0] * 2.0; // offsets
u_cellTypeLayout[2][0] * 2.0 + // offsets
u_cellTypeLayout[2][1]; // thetaOffset
}

View File

@ -0,0 +1,11 @@
// 计算 thetaOffset 和标示在 cell 中取值位置的偏移量 textureOffset
vec2 calThetaOffsetAndOffset(float cellCurrentRow, float cellCurrentColumn, float columnCount, float textureOffset, float columnWidth, float rowHeight) {
if(!hasThetaOffset()) { // 数据纹理中不存在 thetaOffset 的时候取默认值(用户在 style 中传入的是常量)
return vec2(u_thetaOffset, textureOffset);
} else {
vec2 valuePos = nextPos(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset);
float textureThetaOffset = pos2value(valuePos, columnWidth, rowHeight);
return vec2(textureThetaOffset, textureOffset + 1.0);
}
}

View File

@ -766,13 +766,15 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
this.multiPassRenderer.destroy();
// 清除所有属性以及关联的 vao
// 清除所有属性以及关联的 vao == 销毁所有 => model this.models.forEach((model) => model.destroy());
this.styleAttributeService.clearAllAttributes();
// 销毁所有 model
// this.models.forEach((model) => model.destroy());
// 执行每个图层单独的 clearModels 方法 (清除一些额外的 texture、program、buffer 等)
this.layerModel.clearModels();
this.hooks.afterDestroy.call();
// TODO: 给外部使用
// TODO: 清除各个图层自定义的 models 资源
this.layerModel?.clearModels();
// @ts-ignore

View File

@ -70,6 +70,7 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
protected rowCount: number; // 计算得到的当前数据纹理有多少行(高度)
protected cacheStyleProperties: {
// 记录存储上一次样式字段的值
thetaOffset: styleSingle | undefined;
opacity: styleSingle | undefined;
strokeOpacity: styleSingle | undefined;
strokeWidth: styleSingle | undefined;
@ -81,6 +82,7 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
protected cellTypeLayout: number[];
protected stylePropertyesExist: {
// 记录 style 属性是否存在的中间变量
hasThetaOffset: number;
hasOpacity: number;
hasStrokeOpacity: number;
hasStrokeWidth: number;
@ -145,6 +147,7 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
this.cellLength = 0;
this.cellProperties = [];
this.cacheStyleProperties = {
thetaOffset: undefined,
opacity: undefined,
strokeOpacity: undefined,
strokeWidth: undefined,
@ -152,6 +155,7 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
offsets: undefined,
};
this.stylePropertyesExist = {
hasThetaOffset: 0,
hasOpacity: 0,
hasStrokeOpacity: 0,
hasStrokeWidth: 0,
@ -181,6 +185,7 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
this.cellLength = 0; // 清空上一次计算的 cell 的长度
this.stylePropertyesExist = {
// 全量清空上一次是否需要对 style 属性进行数据映射的判断
hasThetaOffset: 0,
hasOpacity: 0,
hasStrokeOpacity: 0,
hasStrokeWidth: 0,
@ -192,18 +197,22 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
public getCellTypeLayout() {
if (this.dataTextureTest) {
return [
// 0
this.rowCount, // 数据纹理有几行
this.DATA_TEXTURE_WIDTH, // 数据纹理有几列
0.0,
0.0,
// 1
this.stylePropertyesExist.hasOpacity, // cell 中是否存在 opacity
this.stylePropertyesExist.hasStrokeOpacity, // cell 中是否存在 strokeOpacity
this.stylePropertyesExist.hasStrokeWidth, // cell 中是否存在 strokeWidth
this.stylePropertyesExist.hasStroke, // cell 中是否存在 stroke
// 2
this.stylePropertyesExist.hasOffsets, // cell 中是否存在 offsets
this.stylePropertyesExist.hasThetaOffset, // cell 中是否存在 thetaOffset
0.0,
0.0,
0.0,
// 3
0.0,
0.0,
0.0,
@ -237,6 +246,8 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
* @returns
*/
public dataTextureNeedUpdate(options: {
// TODO: thetaOffset 目前只有 lineLayer/arc 使用
thetaOffset?: styleSingle;
opacity?: styleSingle;
strokeOpacity?: styleSingle;
strokeWidth?: styleSingle;
@ -245,6 +256,10 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
textOffset?: styleOffset;
}): boolean {
let isUpdate = false;
if (!isEqual(options.thetaOffset, this.cacheStyleProperties.thetaOffset)) {
isUpdate = true;
this.cacheStyleProperties.thetaOffset = options.thetaOffset;
}
if (!isEqual(options.opacity, this.cacheStyleProperties.opacity)) {
isUpdate = true;
this.cacheStyleProperties.opacity = options.opacity;
@ -277,6 +292,8 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
* @param options
*/
public judgeStyleAttributes(options: {
// TODO: 目前 thetaOffset 只有 lineLayer/arc 使用
thetaOffset?: styleSingle;
opacity?: styleSingle;
strokeOpacity?: styleSingle;
strokeWidth?: styleSingle;
@ -325,6 +342,13 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
this.stylePropertyesExist.hasOffsets = 1;
this.cellLength += 2;
}
if (options.thetaOffset !== undefined && !isNumber(options.thetaOffset)) {
// 数据映射
this.cellProperties.push({ attr: 'thetaOffset', count: 1 });
this.stylePropertyesExist.hasThetaOffset = 1;
this.cellLength += 1;
}
// console.log('this.cellLength', this.cellLength)
}
@ -405,7 +429,7 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
const { attr, count } = layout;
const value = cellData[attr];
if (value) {
if (value !== undefined) {
// 数据中存在该属性
if (attr === 'stroke') {
d.push(...rgb2arr(value));

View File

@ -12,6 +12,8 @@ import RasterLayer from './raster';
import EarthLayer from './earth';
import WindLayer from './wind';
// import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin';
import DataMappingPlugin from './plugins/DataMappingPlugin';
import DataSourcePlugin from './plugins/DataSourcePlugin';
@ -143,4 +145,5 @@ export {
RasterLayer,
HeatmapLayer,
EarthLayer,
WindLayer,
};

View File

@ -37,8 +37,11 @@ export default class ArcModel extends BaseModel {
thetaOffset = 0.314,
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
if (this.dataTextureTest && this.dataTextureNeedUpdate({ opacity })) {
this.judgeStyleAttributes({ opacity });
if (
this.dataTextureTest &&
this.dataTextureNeedUpdate({ opacity, thetaOffset })
) {
this.judgeStyleAttributes({ opacity, thetaOffset });
const encodeData = this.layer.getEncodedData();
const { data, width, height } = this.calDataFrame(
this.cellLength,
@ -86,10 +89,10 @@ export default class ArcModel extends BaseModel {
}
return {
u_thetaOffset: thetaOffset,
u_dataTexture: this.dataTexture, // 数据纹理 - 有数据映射的时候纹理中带数据,若没有任何数据映射时纹理是 [1]
u_cellTypeLayout: this.getCellTypeLayout(),
u_thetaOffset: isNumber(thetaOffset) ? thetaOffset : 0.0,
u_opacity: isNumber(opacity) ? opacity : 1.0,
u_textureBlend: textureBlend === 'normal' ? 0.0 : 1.0,
segmentNumber,

View File

@ -30,6 +30,7 @@ varying mat4 styleMappingMat; // 用于将在顶点着色器中计算好的样
#pragma include "styleMapping"
#pragma include "styleMappingCalOpacity"
#pragma include "styleMappingCalThetaOffset"
#pragma include "projection"
#pragma include "project"
@ -39,11 +40,11 @@ float bezier3(vec3 arr, float t) {
float ut = 1. - t;
return (arr.x * ut + arr.y * t) * ut + (arr.y * ut + arr.z * t) * t;
}
vec2 midPoint(vec2 source, vec2 target) {
vec2 midPoint(vec2 source, vec2 target, float arcThetaOffset) {
vec2 center = target - source;
float r = length(center);
float theta = atan(center.y, center.x);
float thetaOffset = u_thetaOffset;
float thetaOffset = arcThetaOffset;
float r2 = r / 2.0 / cos(thetaOffset);
float theta2 = theta + thetaOffset;
vec2 mid = vec2(r2*cos(theta2) + source.x, r2*sin(theta2) + source.y);
@ -59,9 +60,9 @@ vec2 midPoint(vec2 source, vec2 target) {
float getSegmentRatio(float index) {
return smoothstep(0.0, 1.0, index / (segmentNumber - 1.));
}
vec2 interpolate (vec2 source, vec2 target, float t) {
vec2 interpolate (vec2 source, vec2 target, float t, float arcThetaOffset) {
// if the angularDist is PI, linear interpolation is applied. otherwise, use spherical interpolation
vec2 mid = midPoint(source, target);
vec2 mid = midPoint(source, target, arcThetaOffset);
vec3 x = vec3(source.x, mid.x, target.x);
vec3 y = vec3(source.y, mid.y, target.y);
return vec2(bezier3(x ,t), bezier3(y,t));
@ -102,13 +103,17 @@ void main() {
float cellCurrentRow = floor(id * cellCount / columnCount) + 1.0; // 起始点在第几行
float cellCurrentColumn = mod(id * cellCount, columnCount) + 1.0; // 起始点在第几列
// cell 固定顺序 opacity -> strokeOpacity -> strokeWidth -> stroke ...
// cell 固定顺序 opacity -> strokeOpacity -> strokeWidth -> stroke -> thetaOffset...
// 按顺序从 cell 中取值、若没有则自动往下取值
float textureOffset = 0.0; // 在 cell 中取值的偏移量
vec2 opacityAndOffset = calOpacityAndOffset(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset, columnWidth, rowHeight);
styleMappingMat[0][0] = opacityAndOffset.r;
textureOffset = opacityAndOffset.g;
vec2 thetaOffsetAndOffset = calThetaOffsetAndOffset(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset, columnWidth, rowHeight);
styleMappingMat[0][1] = thetaOffsetAndOffset.r;
textureOffset = thetaOffsetAndOffset.g;
// cal style mapping - 数据纹理映射部分的计算
@ -144,8 +149,9 @@ void main() {
styleMappingMat[3].b = d_distance_ratio;
vec4 curr = project_position(vec4(interpolate(source, target, segmentRatio), 0.0, 1.0));
vec4 next = project_position(vec4(interpolate(source, target, nextSegmentRatio), 0.0, 1.0));
// styleMappingMat[0][1] - arcThetaOffset
vec4 curr = project_position(vec4(interpolate(source, target, segmentRatio, styleMappingMat[0][1]), 0.0, 1.0));
vec4 next = project_position(vec4(interpolate(source, target, nextSegmentRatio, styleMappingMat[0][1]), 0.0, 1.0));
// v_normal = getNormal((next.xy - curr.xy) * indexDir, a_Position.y);
//unProjCustomCoord

View File

@ -13,6 +13,7 @@ import { isArray, isFunction, isNumber, isString } from 'lodash';
*/
interface IConfigToUpdate {
thetaOffset?: any;
opacity?: any;
strokeOpacity?: any;
stroke?: any;
@ -48,7 +49,6 @@ function registerStyleAttribute(
function handleStyleDataMapping(configToUpdate: IConfigToUpdate, layer: any) {
if (configToUpdate.opacity) {
// 处理 style 中 opacity 属性的数据映射
handleStyleFloat('opacity', layer, configToUpdate.opacity);
}
@ -60,7 +60,6 @@ function handleStyleDataMapping(configToUpdate: IConfigToUpdate, layer: any) {
if (configToUpdate.strokeOpacity) {
// 处理 style 中 strokeOpacity 属性的数据映射
handleStyleFloat('strokeOpacity', layer, configToUpdate.strokeOpacity);
}
@ -78,6 +77,11 @@ function handleStyleDataMapping(configToUpdate: IConfigToUpdate, layer: any) {
// 处理 style 中 textOffset 属性的数据映射
handleStyleOffsets('textOffset', layer, configToUpdate.textOffset);
}
if (configToUpdate.thetaOffset) {
// 处理 style 中 thetaOffset 属性的数据映射
handleStyleFloat('thetaOffset', layer, configToUpdate.thetaOffset);
}
}
/**

View File

@ -0,0 +1,59 @@
import BaseLayer from '../core/BaseLayer';
import WindModels, { WindModelType } from './models';
interface IWindLayerStyleOptions {
uMin?: number;
uMax?: number;
vMin?: number;
vMax?: number;
fadeOpacity?: number;
speedFactor?: number;
dropRate?: number;
dropRateBump?: number;
opacity?: number;
numParticles?: number;
rampColors?: {
[key: number]: string;
};
sizeScale?: number;
}
export default class WindLayer extends BaseLayer<IWindLayerStyleOptions> {
public type: string = 'WindLayer';
public buildModels() {
const modelType = this.getModelType();
this.layerModel = new WindModels[modelType](this);
this.models = this.layerModel.initModels();
}
public rebuildModels() {
this.models = this.layerModel.buildModels();
}
public renderModels() {
if (this.layerModel) {
this.layerModel.render(); // 独立的渲染流程
}
return this;
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected getDefaultConfig() {
const type = this.getModelType();
const defaultConfig = {
wind: {},
};
return defaultConfig[type];
}
protected getModelType(): WindModelType {
return 'wind';
}
}

View File

@ -0,0 +1,7 @@
import WindModel from './wind';
export type WindModelType = 'wind';
const WindModels: { [key in WindModelType]: any } = {
wind: WindModel,
};
export default WindModels;

View File

@ -0,0 +1,262 @@
// @ts-nocheck
export function createProgram(gl, vshader, fshader) {
// Create shader object
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader); // 创建顶点着色器对象
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader); // 创建片元着色器对象
if (!vertexShader || !fragmentShader) {
return null;
}
// Create a program object
const program = gl.createProgram(); // 创建程序对象
if (!program) {
return null;
}
// Attach the shader objects
gl.attachShader(program, vertexShader); // 绑定着色器对象
gl.attachShader(program, fragmentShader);
// Link the program object
gl.linkProgram(program); // 链接着色器对象
// Check the result of linking
const linked = gl.getProgramParameter(program, gl.LINK_STATUS); // 判断着色器对象是否链接成功
if (!linked) {
const error = gl.getProgramInfoLog(program);
console.warn('Failed to link program: ' + error);
gl.deleteProgram(program);
gl.deleteShader(fragmentShader);
gl.deleteShader(vertexShader);
return null;
}
const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
for (let i = 0; i < numAttributes; i++) {
const attribute = gl.getActiveAttrib(program, i);
program[attribute.name] = gl.getAttribLocation(program, attribute.name);
}
const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i$1 = 0; i$1 < numUniforms; i$1++) {
const uniform = gl.getActiveUniform(program, i$1);
program[uniform.name] = gl.getUniformLocation(program, uniform.name);
}
program.vertexShader = vertexShader;
program.fragmentShader = fragmentShader;
return program;
}
export function loadShader(gl: WebGLRenderingContext, type, source) {
// Create shader object
const shader = gl.createShader(type); // 生成着色器对象
if (shader == null) {
console.warn('unable to create shader');
return null;
}
// Set the shader program
gl.shaderSource(shader, source); // 载入着色器
// Compile the shader
gl.compileShader(shader); // 编译着色器代码
// Check the result of compilation
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); // 判断着色器对象是否生成成功
// gl.SHADER_TYPE、gl.DELETE_STATUS、gl.COMPILE_STATUS
if (!compiled) {
const error = gl.getShaderInfoLog(shader);
console.warn('Failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
export function createTexture(
gl: WebGLRenderingContext,
filter: any,
data: any,
width: number,
height: number,
) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
width,
height,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
data,
);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
export function createDataTexture(
gl: WebGLRenderingContext,
filter: any,
data: any,
) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
export function bindTexture(gl, texture, unit) {
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, texture);
}
export function createBuffer(gl, data) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
return buffer;
}
export function bindAttriBuffer(
gl: WebGLRenderingContext,
attrName: string,
vertices,
count,
program,
) {
const buffer = gl.createBuffer();
if (!buffer) {
console.warn('failed create vertex buffer');
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); // 将缓冲区对象绑定到目标
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // 向缓冲区对象中写入数据
const attr = gl.getAttribLocation(program, attrName);
gl.vertexAttribPointer(attr, count, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(attr);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return { buffer, attr, count };
}
export function bindAttriIndicesBuffer(
gl: WebGLRenderingContext,
indices: Uint8Array,
): WebGLBuffer {
const buffer = gl.createBuffer();
if (!buffer) {
console.warn('failed create vertex buffer');
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return buffer;
}
export function bindUnifrom(gl, unifromName, data, program, vec) {
const uniform = gl.getUniformLocation(program, unifromName);
if (uniform < 0) {
console.warn('无法获取 uniform 变量的存储位置');
}
setUnifrom(gl, uniform, data, vec);
return uniform;
}
export function setUnifrom(gl, location, data, vec) {
switch (vec) {
case 'float':
gl.uniform1f(location, data);
break;
case 'vec2':
gl.uniform2fv(location, data);
break;
case 'vec3':
gl.uniform3fv(location, data);
break;
case 'vec4':
gl.uniform4fv(location, data);
break;
case 'bool':
gl.uniform1i(location, data); // 1 - true 0 - false
break;
case 'sampler2d':
break;
case 'mat4':
gl.uniformMatrix4fv(location, false, data);
break;
}
}
export function initFramebuffer(gl) {
const { drawingBufferWidth, drawingBufferHeight } = gl;
const OFFER_SCREEN_WIDTH = drawingBufferWidth;
const OFFER_SCREEN_HEIGHT = drawingBufferHeight;
const FRAMEBUFFER = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, FRAMEBUFFER);
const depthbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthbuffer);
gl.renderbufferStorage(
gl.RENDERBUFFER,
gl.DEPTH_COMPONENT16,
OFFER_SCREEN_WIDTH,
OFFER_SCREEN_HEIGHT,
);
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER,
depthbuffer,
);
const texture = gl.createTexture();
const textureSize = 1024;
FRAMEBUFFER.texture = texture;
FRAMEBUFFER.width = OFFER_SCREEN_WIDTH;
FRAMEBUFFER.height = OFFER_SCREEN_HEIGHT;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
OFFER_SCREEN_WIDTH,
OFFER_SCREEN_HEIGHT,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null,
);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
0,
);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return { FRAMEBUFFER, OFFER_SCREEN_WIDTH, OFFER_SCREEN_HEIGHT };
}

View File

@ -0,0 +1,251 @@
import {
AttributeType,
gl,
IEncodeFeature,
IModel,
IModelUniform,
ITexture2D,
Point,
} from '@antv/l7-core';
import { FrequencyController, isMini } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { RasterImageTriangulation } from '../../core/triangulation';
import WindFrag from '../shaders/wind_frag.glsl';
import WindVert from '../shaders/wind_vert.glsl';
import { IWind, IWindProps, Wind } from './windRender';
interface IWindLayerStyleOptions {
uMin?: number;
uMax?: number;
vMin?: number;
vMax?: number;
fadeOpacity?: number;
speedFactor?: number;
dropRate?: number;
dropRateBump?: number;
opacity?: number;
numParticles?: number;
rampColors?: {
[key: number]: string;
};
sizeScale?: number;
}
const defaultRampColors = {
0.0: '#3288bd',
0.1: '#66c2a5',
0.2: '#abdda4',
0.3: '#e6f598',
0.4: '#fee08b',
0.5: '#fdae61',
0.6: '#f46d43',
1.0: '#d53e4f',
};
export default class WindModel extends BaseModel {
protected texture: ITexture2D;
private colorModel: IModel;
private wind: IWind;
private imageCoords: [Point, Point];
private sizeScale: number = 0.5;
// https://mapbox.github.io/webgl-wind/demo/
// source: 'http://nomads.ncep.noaa.gov',
private frequency = new FrequencyController(7.2);
public render() {
// TODO: 控制风场的平均更新频率
this.frequency.run(() => {
this.drawWind();
});
this.drawColorMode();
}
public getUninforms(): IModelUniform {
throw new Error('Method not implemented.');
}
public initModels() {
const { createTexture2D } = this.rendererService;
const source = this.layer.getSource();
this.texture = createTexture2D({
height: 0,
width: 0,
});
const glContext = this.rendererService.getGLContext();
this.imageCoords = source.data.dataArray[0].coordinates as [Point, Point];
source.data.images.then((imageData: HTMLImageElement[]) => {
const {
uMin = -21.32,
uMax = 26.8,
vMin = -21.57,
vMax = 21.42,
fadeOpacity = 0.996,
speedFactor = 0.25,
dropRate = 0.003,
dropRateBump = 0.01,
rampColors = defaultRampColors,
sizeScale = 0.5,
} = this.layer.getLayerConfig() as IWindLayerStyleOptions;
this.sizeScale = sizeScale;
const { imageWidth, imageHeight } = this.getWindSize();
const options: IWindProps = {
glContext,
imageWidth,
imageHeight,
fadeOpacity,
speedFactor,
dropRate,
dropRateBump,
rampColors,
};
this.wind = new Wind(options);
// imageData[0] 风场图
this.wind.setWind({
uMin,
uMax,
vMin,
vMax,
image: imageData[0],
});
this.texture = createTexture2D({
data: imageData[0],
width: imageData[0].width,
height: imageData[0].height,
});
this.layerService.updateLayerRenderList();
this.layerService.renderLayers();
});
this.colorModel = this.layer.buildLayerModel({
moduleName: 'WindLayer',
vertexShader: WindVert,
fragmentShader: WindFrag,
triangulation: RasterImageTriangulation,
primitive: gl.TRIANGLES,
depth: { enable: false },
blend: this.getBlend(),
});
return [this.colorModel];
}
public getWindSize() {
const p1 = this.mapService.lngLatToPixel(this.imageCoords[0]);
const p2 = this.mapService.lngLatToPixel(this.imageCoords[1]);
const imageWidth = Math.floor((p2.x - p1.x) * this.sizeScale);
const imageHeight = Math.floor((p1.y - p2.y) * this.sizeScale);
return { imageWidth, imageHeight };
}
public buildModels() {
return this.initModels();
}
public clearModels(): void {
this.texture?.destroy();
this.wind?.destroy();
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected registerBuiltinAttributes() {
// point layer size;
this.styleAttributeService.registerStyleAttribute({
name: 'uv',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Uv',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 2,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
return [vertex[3], vertex[4]];
},
},
});
}
private drawWind() {
if (this.wind) {
const {
uMin = -21.32,
uMax = 26.8,
vMin = -21.57,
vMax = 21.42,
numParticles = 65535,
fadeOpacity = 0.996,
speedFactor = 0.25,
dropRate = 0.003,
dropRateBump = 0.01,
rampColors = defaultRampColors,
sizeScale = 0.5,
} = this.layer.getLayerConfig() as IWindLayerStyleOptions;
if (typeof sizeScale === 'number' && sizeScale !== this.sizeScale) {
this.sizeScale = sizeScale;
const { imageWidth, imageHeight } = this.getWindSize();
this.wind.reSize(imageWidth, imageHeight);
}
this.wind.updateWindDir(uMin, uMax, vMin, vMax);
this.wind.updateParticelNum(numParticles);
this.wind.updateColorRampTexture(rampColors);
this.wind.fadeOpacity = fadeOpacity;
this.wind.speedFactor = speedFactor;
this.wind.dropRate = dropRate;
this.wind.dropRateBump = dropRateBump;
const { d, w, h } = this.wind.draw();
// TODO: 恢复 L7 渲染流程中 gl 状态
this.rendererService.setBaseState();
this.texture.update({
data: d,
width: w,
height: h,
});
}
}
private drawColorMode() {
const { opacity } = this.layer.getLayerConfig() as IWindLayerStyleOptions;
this.colorModel.draw({
uniforms: {
u_opacity: opacity || 1.0,
u_texture: this.texture,
},
});
}
}

View File

@ -0,0 +1,542 @@
import * as glUtils from './utils';
import {
drawFrag,
drawVert,
fullScreenFrag,
fullScreenVert,
updateFrag,
updateVert,
} from './windShader';
function getColorRamp(colors: { [key: number]: string }) {
let canvas = document.createElement('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
canvas.width = 256;
canvas.height = 1;
const gradient = ctx.createLinearGradient(0, 0, 256, 0);
for (const stop of Object.keys(colors)) {
gradient.addColorStop(+stop, colors[+stop]);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 1);
// @ts-ignore dispose canvas element
canvas = null;
return new Uint8Array(ctx.getImageData(0, 0, 256, 1).data);
}
function bindAttribute(
gl: WebGLRenderingContext,
buffer: any,
attribute: any,
numComponents: any,
) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(attribute);
gl.vertexAttribPointer(attribute, numComponents, gl.FLOAT, false, 0, 0);
}
function bindFramebuffer(
gl: WebGLRenderingContext,
framebuffer: any,
texture: any,
) {
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
if (texture) {
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
0,
);
}
}
export interface IWindData {
uMin: number;
uMax: number;
vMin: number;
vMax: number;
image: HTMLImageElement;
}
export interface IWind {
width: number;
height: number;
fadeOpacity: number;
speedFactor: number;
dropRate: number;
dropRateBump: number;
setWind: (windData: IWindData) => void;
draw: () => { d: Uint8Array; w: number; h: number };
updateParticelNum: (num: number) => void;
updateWindDir: (
uMin: number,
uMax: number,
vMin: number,
vMax: number,
) => void;
updateColorRampTexture: (rampColors: { [key: number]: string }) => void;
reSize: (width: number, height: number) => void;
destroy: () => void;
}
export interface IWindProps {
glContext: WebGLRenderingContext;
imageWidth: number;
imageHeight: number;
fadeOpacity: number;
speedFactor: number;
dropRate: number;
dropRateBump: number;
rampColors: { [key: number]: string };
}
export class Wind {
public width: number = 512;
public height: number = 512;
public pixels: Uint8Array;
public fadeOpacity: number;
public speedFactor: number;
public dropRate: number;
public dropRateBump: number;
private gl: WebGLRenderingContext;
private drawProgram: WebGLProgram;
private fullScreenProgram: WebGLProgram;
private updateProgram: WebGLProgram;
private rampColors: { [key: number]: string };
private numParticles: number = 65536;
private numParticlesSize: number;
private particleStateResolution: number;
private quadBuffer: WebGLBuffer | null;
private particleIndexBuffer: WebGLBuffer | null;
private framebuffer: WebGLFramebuffer | null;
private colorRampTexture: WebGLTexture | null;
private backgroundTexture: WebGLTexture | null;
private screenTexture: WebGLTexture | null;
private particleStateTexture0: WebGLTexture | null;
private particleStateTexture1: WebGLTexture | null;
private windTexture: WebGLTexture | null;
private windData: IWindData;
constructor(options: IWindProps) {
this.gl = options.glContext;
this.width = options.imageWidth;
this.height = options.imageHeight;
this.fadeOpacity = options.fadeOpacity;
this.speedFactor = options.speedFactor;
this.dropRate = options.dropRate;
this.dropRateBump = options.dropRateBump;
this.rampColors = options.rampColors;
this.init();
}
public init() {
const gl = this.gl;
this.fadeOpacity = 0.996; // how fast the particle trails fade on each frame
this.speedFactor = 0.25; // how fast the particles move
this.dropRate = 0.003; // how often the particles move to a random place
this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed
this.drawProgram = glUtils.createProgram(gl, drawVert, drawFrag);
this.fullScreenProgram = glUtils.createProgram(
gl,
fullScreenVert,
fullScreenFrag,
);
this.updateProgram = glUtils.createProgram(gl, updateVert, updateFrag);
this.quadBuffer = glUtils.createBuffer(
gl,
new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]),
);
this.framebuffer = gl.createFramebuffer();
this.colorRampTexture = glUtils.createTexture(
this.gl,
this.gl.LINEAR,
getColorRamp(this.rampColors),
16,
16,
);
const emptyPixels = new Uint8Array(this.width * this.height * 4);
// screen textures to hold the drawn screen for the previous and the current frame
this.backgroundTexture = glUtils.createTexture(
gl,
gl.NEAREST,
emptyPixels,
this.width,
this.height,
);
this.screenTexture = glUtils.createTexture(
gl,
gl.NEAREST,
emptyPixels,
this.width,
this.height,
);
// we create a square texture where each pixel will hold a particle position encoded as RGBA
const particleRes = (this.particleStateResolution = Math.ceil(
Math.sqrt(this.numParticles),
));
// particleRes size
this.numParticlesSize = particleRes * particleRes;
const particleState = new Uint8Array(this.numParticlesSize * 4);
for (let i = 0; i < particleState.length; i++) {
particleState[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions
}
// textures to hold the particle state for the current and the next frame
this.particleStateTexture0 = glUtils.createTexture(
gl,
gl.NEAREST,
particleState,
particleRes,
particleRes,
);
this.particleStateTexture1 = glUtils.createTexture(
gl,
gl.NEAREST,
particleState,
particleRes,
particleRes,
);
const particleIndices = new Float32Array(this.numParticlesSize);
for (let i$1 = 0; i$1 < this.numParticlesSize; i$1++) {
particleIndices[i$1] = i$1;
}
this.particleIndexBuffer = glUtils.createBuffer(gl, particleIndices);
}
public setWind(windData: IWindData) {
this.windData = windData;
this.windTexture = glUtils.createDataTexture(
this.gl,
this.gl.LINEAR,
windData.image,
);
}
/**
*
* @param num
*/
public updateParticelNum(num: number) {
const gl = this.gl;
if (num !== this.numParticles) {
this.numParticles = num; // params number
// we create a square texture where each pixel will hold a particle position encoded as RGBA
const particleRes = (this.particleStateResolution = Math.ceil(
Math.sqrt(this.numParticles),
));
this.numParticlesSize = particleRes * particleRes;
const particleState = new Uint8Array(this.numParticlesSize * 4);
for (let i = 0; i < particleState.length; i++) {
particleState[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions
}
// textures to hold the particle state for the current and the next frame
this.particleStateTexture0 = glUtils.createTexture(
gl,
gl.NEAREST,
particleState,
particleRes,
particleRes,
);
this.particleStateTexture1 = glUtils.createTexture(
gl,
gl.NEAREST,
particleState,
particleRes,
particleRes,
);
const particleIndices = new Float32Array(this.numParticlesSize);
for (let i$1 = 0; i$1 < this.numParticlesSize; i$1++) {
particleIndices[i$1] = i$1;
}
this.particleIndexBuffer = glUtils.createBuffer(gl, particleIndices);
}
}
/**
*
* @param uMin
* @param uMax
* @param vMin
* @param vMax
*/
public updateWindDir(uMin: number, uMax: number, vMin: number, vMax: number) {
this.windData.uMin = uMin;
this.windData.uMax = uMax;
this.windData.vMin = vMin;
this.windData.vMax = vMax;
}
/**
* update rampColors
* @param rampColors
*/
public updateColorRampTexture(rampColors: { [key: number]: string }) {
if (this.isColorChanged(rampColors)) {
this.rampColors = rampColors;
const gl = this.gl;
gl.deleteTexture(this.colorRampTexture);
this.colorRampTexture = glUtils.createTexture(
gl,
gl.LINEAR,
getColorRamp(rampColors),
16,
16,
);
}
}
public isColorChanged(rampColors: { [key: number]: string }): boolean {
const keys = Object.keys(rampColors);
for (const item of keys) {
const key = Number(item);
// exist new key -> color need update
if (!this.rampColors[key]) {
return true;
}
// value changed -> color need update
if (this.rampColors[key] && this.rampColors[key] !== rampColors[key]) {
return true;
}
}
return false;
}
public reSize(width: number, height: number) {
if (width !== this.width || height !== this.height) {
const gl = this.gl;
gl.deleteTexture(this.backgroundTexture);
gl.deleteTexture(this.screenTexture);
this.width = width;
this.height = height;
const emptyPixels = new Uint8Array(width * height * 4);
// screen textures to hold the drawn screen for the previous and the current frame
this.backgroundTexture = glUtils.createTexture(
gl,
gl.NEAREST,
emptyPixels,
width,
height,
);
this.screenTexture = glUtils.createTexture(
gl,
gl.NEAREST,
emptyPixels,
width,
height,
);
}
}
public draw() {
if (this.windData?.image) {
const gl = this.gl;
glUtils.bindTexture(gl, this.windTexture, 0);
glUtils.bindTexture(gl, this.particleStateTexture0, 1);
this.drawScreen(); // draw Particles into framebuffer
this.updateParticles();
return { d: this.pixels, w: this.width, h: this.height };
} else {
return { d: new Uint8Array([0, 0, 0, 0]), w: 1, h: 1 };
}
}
public drawScreen() {
const gl = this.gl;
// draw the screen into a temporary framebuffer to retain it as the background on the next frame
bindFramebuffer(gl, this.framebuffer, this.screenTexture);
gl.viewport(0, 0, this.width, this.height);
this.drawFullTexture(this.backgroundTexture, this.fadeOpacity);
this.drawParticles();
gl.disable(gl.BLEND);
this.pixels = new Uint8Array(4 * this.width * this.height);
gl.readPixels(
0,
0,
this.width,
this.height,
gl.RGBA,
gl.UNSIGNED_BYTE,
this.pixels,
);
bindFramebuffer(gl, null, null);
gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
// save the current screen as the background for the next frame
const temp = this.backgroundTexture;
this.backgroundTexture = this.screenTexture;
this.screenTexture = temp;
}
public drawFullTexture(texture: any, opacity: number) {
const gl = this.gl;
const program = this.fullScreenProgram as any;
gl.useProgram(program);
// bindAttribute(gl, this.quadBuffer, program.a_pos, 2);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadBuffer);
gl.vertexAttribPointer(program.a_pos, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(program.a_pos);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
glUtils.bindTexture(gl, texture, 2);
gl.uniform1i(program.u_screen, 2);
gl.uniform1f(program.u_opacity, opacity);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// gl.drawArrays(gl.POINTS, 0, 6);
}
public drawParticles() {
const gl = this.gl;
const program = this.drawProgram as any;
gl.useProgram(program);
bindAttribute(gl, this.particleIndexBuffer, program.a_index, 1);
glUtils.bindTexture(gl, this.colorRampTexture, 2);
gl.uniform1i(program.u_wind, 0);
gl.uniform1i(program.u_particles, 1);
gl.uniform1i(program.u_color_ramp, 2);
gl.uniform1f(program.u_particles_res, this.particleStateResolution);
gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin);
gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax);
gl.drawArrays(gl.POINTS, 0, this.numParticlesSize);
}
public updateParticles() {
const gl = this.gl;
bindFramebuffer(gl, this.framebuffer, this.particleStateTexture1);
gl.viewport(
0,
0,
this.particleStateResolution,
this.particleStateResolution,
);
const program = this.updateProgram as any;
gl.useProgram(program);
bindAttribute(gl, this.quadBuffer, program.a_pos, 2);
gl.uniform1i(program.u_wind, 0);
gl.uniform1i(program.u_particles, 1);
gl.uniform1f(program.u_rand_seed, Math.random());
gl.uniform2f(
program.u_wind_res,
this.windData.image.width * 2,
this.windData.image.height * 2,
);
gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin);
gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax);
gl.uniform1f(program.u_speed_factor, this.speedFactor);
gl.uniform1f(program.u_drop_rate, this.dropRate);
gl.uniform1f(program.u_drop_rate_bump, this.dropRateBump);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// swap the particle state textures so the new one becomes the current one
const temp = this.particleStateTexture0;
this.particleStateTexture0 = this.particleStateTexture1;
this.particleStateTexture1 = temp;
bindFramebuffer(gl, null, null);
// gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
}
public destroy() {
// private drawProgram: WebGLProgram;
// private fullScreenProgram: WebGLProgram;
// private updateProgram: WebGLProgram;
// private quadBuffer: WebGLBuffer | null;
// private particleIndexBuffer: WebGLBuffer | null;
// private framebuffer: WebGLFramebuffer | null;
// private colorRampTexture: WebGLTexture | null;
// private backgroundTexture: WebGLTexture | null;
// private screenTexture: WebGLTexture | null;
// private particleStateTexture0: WebGLTexture | null;
// private particleStateTexture1: WebGLTexture | null;
// private windTexture: WebGLTexture | null;
this.gl.deleteBuffer(this.quadBuffer);
this.gl.deleteBuffer(this.particleIndexBuffer);
this.gl.deleteFramebuffer(this.framebuffer);
// @ts-ignore
this.gl.deleteShader(this.drawProgram.vertexShader);
// @ts-ignore
this.gl.deleteShader(this.drawProgram.fragmentShader);
this.gl.deleteProgram(this.drawProgram);
// @ts-ignore
this.gl.deleteShader(this.fullScreenProgram.vertexShader);
// @ts-ignore
this.gl.deleteShader(this.fullScreenProgram.fragmentShader);
this.gl.deleteProgram(this.fullScreenProgram);
// @ts-ignore
this.gl.deleteShader(this.updateProgram.vertexShader);
// @ts-ignore
this.gl.deleteShader(this.updateProgram.fragmentShader);
this.gl.deleteProgram(this.updateProgram);
this.gl.deleteTexture(this.colorRampTexture);
this.gl.deleteTexture(this.backgroundTexture);
this.gl.deleteTexture(this.screenTexture);
this.gl.deleteTexture(this.particleStateTexture0);
this.gl.deleteTexture(this.particleStateTexture1);
this.gl.deleteTexture(this.windTexture);
}
}

View File

@ -0,0 +1,156 @@
/**
* drawProgram drawVert drawFrag
* screenProgram screenVert screenFrag
* updateProgram updateVert updateFrag
* fullScreenProgram fullScreenVert fullScreenFrag
*/
export const drawVert = `
precision mediump float;
attribute float a_index;
uniform sampler2D u_particles;
uniform float u_particles_res;
varying vec2 v_particle_pos;
void main() {
vec4 color = texture2D(u_particles, vec2(
fract(a_index / u_particles_res),
floor(a_index / u_particles_res) / u_particles_res)
);
// decode current particle position from the pixel's RGBA value
v_particle_pos = vec2( color.r / 255.0 + color.b, color.g / 255.0 + color.a);
gl_PointSize = 1.0;
gl_Position = vec4(2.0 * v_particle_pos.x - 1.0, 1.0 - 2.0 * v_particle_pos.y, 0, 1);
}`;
export const drawFrag = `
precision mediump float;
uniform sampler2D u_wind;
uniform vec2 u_wind_min;
uniform vec2 u_wind_max;
uniform sampler2D u_color_ramp;
varying vec2 v_particle_pos;
void main() {
vec2 velocity = mix(u_wind_min, u_wind_max, texture2D(u_wind, v_particle_pos).rg);
float speed_t = length(velocity) / length(u_wind_max);
// color ramp is encoded in a 16x16 texture
vec2 ramp_pos = vec2( fract(16.0 * speed_t), floor(16.0 * speed_t) / 16.0);
gl_FragColor = texture2D(u_color_ramp, ramp_pos);
}`;
export const updateVert = `
precision mediump float;
attribute vec2 a_pos;
varying vec2 v_tex_pos;
void main() {
v_tex_pos = a_pos;
gl_Position = vec4(1.0 - 2.0 * a_pos, 0, 1);
// framebuffer 始终用铺满屏幕的 texture
}`;
export const updateFrag = `
precision highp float;
uniform sampler2D u_particles;
uniform sampler2D u_wind;
uniform vec2 u_wind_res;
uniform vec2 u_wind_min;
uniform vec2 u_wind_max;
uniform float u_rand_seed;
uniform float u_speed_factor;
uniform float u_drop_rate;
uniform float u_drop_rate_bump;
varying vec2 v_tex_pos;
// pseudo-random generator
const vec3 rand_constants = vec3(12.9898, 78.233, 4375.85453);
float rand(const vec2 co) {
float t = dot(rand_constants.xy, co);
return fract(sin(t) * (rand_constants.z + t));
}
// wind speed lookup; use manual bilinear filtering based on 4 adjacent pixels for smooth interpolation
vec2 lookup_wind(const vec2 uv) {
// return texture2D(u_wind, uv).rg; // lower-res hardware filtering
vec2 px = 1.0 / u_wind_res;
vec2 vc = (floor(uv * u_wind_res)) * px;
vec2 f = fract(uv * u_wind_res);
vec2 tl = texture2D(u_wind, vc).rg;
vec2 tr = texture2D(u_wind, vc + vec2(px.x, 0)).rg;
vec2 bl = texture2D(u_wind, vc + vec2(0, px.y)).rg;
vec2 br = texture2D(u_wind, vc + px).rg;
return mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y);
}
void main() {
vec4 color = texture2D(u_particles, v_tex_pos);
vec2 pos = vec2(
color.r / 255.0 + color.b,
color.g / 255.0 + color.a); // decode particle position from pixel RGBA
vec2 velocity = mix(u_wind_min, u_wind_max, lookup_wind(pos));
float speed_t = length(velocity) / length(u_wind_max);
// take EPSG:4236 distortion into account for calculating where the particle moved
float distortion = cos(radians(pos.y * 180.0 - 90.0));
vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;
// update particle position, wrapping around the date line
pos = fract(1.0 + pos + offset);
// a random seed to use for the particle drop
vec2 seed = (pos + v_tex_pos) * u_rand_seed;
// drop rate is a chance a particle will restart at random position, to avoid degeneration
float drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;
float drop = step(1.0 - drop_rate, rand(seed));
vec2 random_pos = vec2(
rand(seed + 1.3),
rand(seed + 2.1));
pos = mix(pos, random_pos, drop);
// encode the new particle position back into RGBA
gl_FragColor = vec4(
fract(pos * 255.0),
floor(pos * 255.0) / 255.0);
}`;
export const fullScreenVert = `
precision mediump float;
attribute vec2 a_pos;
varying vec2 v_tex_pos;
void main() {
v_tex_pos = a_pos;
gl_Position = vec4(1.0 - 2.0 * a_pos, 0.0, 1.0);
gl_PointSize = 100.0;
}`;
export const fullScreenFrag = `
precision mediump float;
uniform sampler2D u_screen;
uniform float u_opacity;
varying vec2 v_tex_pos;
void main() {
vec4 color = texture2D(u_screen, 1.0 - v_tex_pos);
// a hack to guarantee opacity fade out even with a value close to 1.0
gl_FragColor = vec4(floor(255.0 * color * u_opacity) / 255.0);
}`;

View File

@ -0,0 +1,9 @@
precision mediump float;
uniform float u_opacity: 1.0;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_texture,vec2(v_texCoord.x,v_texCoord.y));
gl_FragColor = color;
gl_FragColor.a *= u_opacity;
}

View File

@ -0,0 +1,17 @@
precision highp float;
uniform mat4 u_ModelMatrix;
uniform mat4 u_Mvp;
attribute vec3 a_Position;
attribute vec2 a_Uv;
varying vec2 v_texCoord;
#pragma include "projection"
void main() {
v_texCoord = a_Uv;
vec4 project_pos = project_position(vec4(a_Position, 1.0));
// gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x
gl_Position = u_Mvp * (vec4(project_pos.xy,0., 1.0));
} else {
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
}
}

View File

@ -6,3 +6,21 @@ export function bindAll(fns: string[], context: any) {
context[fn] = context[fn].bind(context);
});
}
// 频率控制器
export class FrequencyController {
private duration: number = 16;
private timestamp: number = new Date().getTime();
constructor(duration: number = 16) {
this.duration = duration;
}
public run(callback: () => any) {
const currentTime = new Date().getTime();
const timeCut = currentTime - this.timestamp;
this.timestamp = currentTime;
if (timeCut >= this.duration) {
callback();
}
}
}

View File

@ -0,0 +1,176 @@
// @ts-ignore
import { Scene, WindLayer, PointLayer } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as React from 'react';
import * as dat from 'dat.gui';
export default class WindMap extends React.Component {
// @ts-ignore
private scene: Scene;
private gui: any;
public componentWillUnmount() {
if (this.gui) {
this.gui.destroy();
}
this.scene.destroy();
}
public async componentDidMount() {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [40, 30.3628],
pitch: 0,
style: 'normal',
zoom: 2,
viewMode: '3D',
}),
});
this.scene = scene;
const pointLayer = new PointLayer({ zIndex: 1 })
.source(
[
{
lng: 121.107846,
lat: 30.267069,
},
],
{
parser: {
type: 'json',
x: 'lng',
y: 'lat',
},
},
)
.shape('circle')
.color('#f00')
.active(true)
.size(40)
.style({
stroke: '#fff',
storkeWidth: 2,
});
scene.on('loaded', () => {
scene.addLayer(pointLayer);
const styleOptions = {
uMin: -21.32,
uMax: 26.8,
vMin: -21.57,
vMax: 21.42,
numParticles: 65535,
fadeOpacity: 0.996,
rampColors: {
0.0: '#3288bd',
0.1: '#66c2a5',
0.2: '#abdda4',
0.3: '#e6f598',
0.4: '#fee08b',
0.5: '#fdae61',
0.6: '#f46d43', // f46d43
1.0: '#d53e4f',
},
sizeScale: 0.5,
};
const layer = new WindLayer({});
layer
.source(
'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*wcU8S5xMEDYAAAAAAAAAAAAAARQnAQ',
{
parser: {
type: 'image',
extent: [-180, -85, 180, 85],
},
},
)
.animate(true)
.style({
uMin: styleOptions.uMin,
uMax: styleOptions.uMax,
vMin: styleOptions.vMin,
vMax: styleOptions.vMax,
fadeOpacity: styleOptions.fadeOpacity,
numParticles: styleOptions.numParticles,
rampColors: styleOptions.rampColors,
sizeScale: styleOptions.sizeScale,
});
scene.addLayer(layer);
/*** 运行时修改样式属性 ***/
const gui = new dat.GUI();
this.gui = gui;
const pointFolder = gui.addFolder('风场数据');
pointFolder
.add(styleOptions, 'numParticles', 0, 65535, 1)
.onChange((num: number) => {
layer.style({
numParticles: num,
});
});
pointFolder
.add(styleOptions, 'uMin', -100, 100, 1)
.onChange((num: number) => {
layer.style({
uMin: num,
});
});
pointFolder
.add(styleOptions, 'fadeOpacity', 0.9, 1, 0.01)
.onChange((num: number) => {
layer.style({
fadeOpacity: num,
});
});
pointFolder
.add(styleOptions, 'sizeScale', 0, 2, 0.01)
.onChange((num: number) => {
layer.style({
sizeScale: num,
});
});
pointFolder
.addColor(styleOptions.rampColors, '0.6')
.onChange((color: string) => {
layer.style({
rampColors: {
0.0: '#3288bd',
0.1: '#66c2a5',
0.2: '#abdda4',
0.3: '#e6f598',
0.4: '#fee08b',
0.5: '#fdae61',
0.6: color,
1.0: '#d53e4f',
},
});
});
});
}
public render() {
return (
<>
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
</>
);
}
}

View File

@ -0,0 +1,115 @@
import { LineLayer, Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as React from 'react';
export default class PlaneLine extends React.Component {
// @ts-ignore
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
pitch: 40,
center: [110, 35.443],
zoom: 5,
viewMode: '3D',
style: 'dark',
}),
});
this.scene = scene;
scene.addImage(
'plane',
'https://gw.alipayobjects.com/zos/bmw-prod/0ca1668e-38c2-4010-8568-b57cb33839b9.svg',
);
// 106.6400729259405 29.72018042111331 重庆
// 116.5883553580003 40.07680509701226 北京
let originData = {
lng1: 106.6400729259405,
lat1: 29.72018042111331,
lng2: 116.5883553580003,
lat2: 40.07680509701226,
};
scene.on('loaded', () => {
let data = [];
for (let i = 0; i < 11; i++) {
data.push({
thetaOffset: -0.5 + i * 0.1,
...originData,
});
}
const layer = new LineLayer({
blend: 'normal',
})
.source(data, {
parser: {
type: 'json',
x: 'lng1',
y: 'lat1',
x1: 'lng2',
y1: 'lat2',
},
})
.size(1)
.shape('arc')
.color('#ccc')
.style({
opacity: 1,
thetaOffset: 'thetaOffset',
});
scene.addLayer(layer);
const layer2 = new LineLayer({
blend: 'normal',
})
.source(data, {
parser: {
type: 'json',
x: 'lng1',
y: 'lat1',
x1: 'lng2',
y1: 'lat2',
},
})
.size(15)
.texture('plane')
.shape('arc')
.color('#8C1EB2')
.style({
opacity: 1,
thetaOffset: 'thetaOffset',
lineTexture: true, // 开启线的贴图功能
iconStep: 20, // 设置贴图纹理的间距
textureBlend: 'replace',
})
.animate({
duration: 0.2,
interval: 0.5,
trailLength: 1,
});
scene.addLayer(layer2);
});
}
public render() {
return (
<>
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
</>
);
}
}

View File

@ -66,6 +66,9 @@ import ShapeUpdate from './components/shapeUpdate'
import AmapPlugin from './components/plugin'
import PointUV from './components/pointUV'
import DestroyClear from './components/destroyClear'
import PlaneLine from './components/planeLine'
import WindMap from './components/amap2demo_wind'
// @ts-ignore
storiesOf('地图方法', module)
@ -132,6 +135,9 @@ storiesOf('地图方法', module)
.add('测试销毁', () => <Amap2demo_destroy/>)
.add('ShapeUpdate', () => <ShapeUpdate/>)
.add('WindMap', () => <WindMap/>)
.add('AmapPlugin', () => <AmapPlugin/>)
.add('PointUV', () => <PointUV/>)
.add('DestroyClear', () => <DestroyClear/>)
.add('PlaneLine', () => <PlaneLine/>)

View File

@ -44,6 +44,7 @@ export default class ScaleComponent extends React.Component {
});
scene.addLayer(layer);
const layer2 = new PolygonLayer({ blend: 'normal' })
.source(data)
.size(1)