mirror of https://gitee.com/antv-l7/antv-l7
Merge branch 'master' of https://github.com/antvis/L7
This commit is contained in:
commit
045986782e
|
@ -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: '',
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import WindModel from './wind';
|
||||
export type WindModelType = 'wind';
|
||||
|
||||
const WindModels: { [key in WindModelType]: any } = {
|
||||
wind: WindModel,
|
||||
};
|
||||
export default WindModels;
|
|
@ -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 };
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}`;
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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/>)
|
||||
|
|
|
@ -44,6 +44,7 @@ export default class ScaleComponent extends React.Component {
|
|||
});
|
||||
scene.addLayer(layer);
|
||||
|
||||
|
||||
const layer2 = new PolygonLayer({ blend: 'normal' })
|
||||
.source(data)
|
||||
.size(1)
|
||||
|
|
Loading…
Reference in New Issue