2117 lines
83 KiB
JavaScript
2117 lines
83 KiB
JavaScript
/**
|
||
* Mars3D平台插件,支持气象 风向图 功能插件 mars3d-wind
|
||
*
|
||
* 版本信息:v3.4.7
|
||
* 编译日期:2022-09-15 16:25:31
|
||
* 版权所有:Copyright by 火星科技 http://mars3d.cn
|
||
* 使用单位:安徽XX有限公司 ,2021-08-18
|
||
*/
|
||
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, (window.mars3d || require('mars3d'))) :
|
||
typeof define === 'function' && define.amd ? define(['exports', 'mars3d'], factory) :
|
||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["mars3d-wind"] = {}, global.mars3d));
|
||
})(this, (function (exports, mars3d) { 'use strict';
|
||
|
||
function _interopNamespace(e) {
|
||
if (e && e.__esModule) return e;
|
||
var n = Object.create(null);
|
||
if (e) {
|
||
Object.keys(e).forEach(function (k) {
|
||
if (k !== 'default') {
|
||
var d = Object.getOwnPropertyDescriptor(e, k);
|
||
Object.defineProperty(n, k, d.get ? d : {
|
||
enumerable: true,
|
||
get: function () { return e[k]; }
|
||
});
|
||
}
|
||
});
|
||
}
|
||
n["default"] = e;
|
||
return n;
|
||
}
|
||
|
||
var mars3d__namespace = /*#__PURE__*/_interopNamespace(mars3d);
|
||
|
||
/**
|
||
* 风场相关 静态方法,【需要引入 mars3d-wind 插件库】
|
||
* @module WindUtil
|
||
*/
|
||
const Cesium$7 = mars3d__namespace.Cesium;
|
||
|
||
/**
|
||
* 风速风向 转 U值
|
||
*
|
||
* @export
|
||
* @param {Number} speed 风速
|
||
* @param {Number} direction 风向
|
||
* @return {Number} U值
|
||
*/
|
||
function getU(speed, direction) {
|
||
const u = speed * Math.cos(Cesium$7.Math.toRadians(direction));
|
||
return u
|
||
}
|
||
|
||
/**
|
||
* 风速风向 转 V值
|
||
*
|
||
* @export
|
||
* @param {Number} speed 风速
|
||
* @param {Number} direction 风向
|
||
* @return {Number} V值
|
||
*/
|
||
function getV(speed, direction) {
|
||
const v = speed * Math.sin(Cesium$7.Math.toRadians(direction));
|
||
return v
|
||
}
|
||
|
||
/**
|
||
* UV值 转 风速, 风速是uv分量的平方和
|
||
*
|
||
* @export
|
||
* @param {Number} u U值
|
||
* @param {Number} v V值
|
||
* @return {Number} 风速
|
||
*/
|
||
function getSpeed(u, v) {
|
||
const speed = Math.sqrt(Math.pow(u, 2) + Math.pow(v, 2));
|
||
return speed
|
||
}
|
||
|
||
/**
|
||
* UV 转 风向
|
||
*
|
||
* @export
|
||
* @param {Number} u U值
|
||
* @param {Number} v V值
|
||
* @return {Number} 风向
|
||
*/
|
||
function getDirection(u, v) {
|
||
let direction = Cesium$7.Math.toDegrees(Math.atan2(v, u));
|
||
direction += direction < 0 ? 360 : 0;
|
||
|
||
return direction
|
||
}
|
||
|
||
var WindUtil = {
|
||
__proto__: null,
|
||
getU: getU,
|
||
getV: getV,
|
||
getSpeed: getSpeed,
|
||
getDirection: getDirection
|
||
};
|
||
|
||
const Cesium$6 = mars3d__namespace.Cesium;
|
||
|
||
class CustomPrimitive {
|
||
constructor(options) {
|
||
this.commandType = options.commandType;
|
||
|
||
this.geometry = options.geometry;
|
||
this.attributeLocations = options.attributeLocations;
|
||
this.primitiveType = options.primitiveType;
|
||
|
||
this.uniformMap = options.uniformMap;
|
||
|
||
this.vertexShaderSource = options.vertexShaderSource;
|
||
this.fragmentShaderSource = options.fragmentShaderSource;
|
||
|
||
this.rawRenderState = options.rawRenderState;
|
||
this.framebuffer = options.framebuffer;
|
||
|
||
this.outputTexture = options.outputTexture;
|
||
|
||
this.autoClear = options.autoClear ?? false;
|
||
this.preExecute = options.preExecute;
|
||
|
||
this.show = true;
|
||
this.commandToExecute = undefined;
|
||
this.clearCommand = undefined;
|
||
if (this.autoClear) {
|
||
this.clearCommand = new Cesium$6.ClearCommand({
|
||
color: new Cesium$6.Color(0.0, 0.0, 0.0, 0.0),
|
||
depth: 1.0,
|
||
framebuffer: this.framebuffer,
|
||
pass: Cesium$6.Pass.OPAQUE
|
||
});
|
||
}
|
||
}
|
||
|
||
createCommand(context) {
|
||
switch (this.commandType) {
|
||
case "Draw": {
|
||
const vertexArray = Cesium$6.VertexArray.fromGeometry({
|
||
context: context,
|
||
geometry: this.geometry,
|
||
attributeLocations: this.attributeLocations,
|
||
bufferUsage: Cesium$6.BufferUsage.STATIC_DRAW
|
||
});
|
||
|
||
const shaderProgram = Cesium$6.ShaderProgram.fromCache({
|
||
context: context,
|
||
attributeLocations: this.attributeLocations,
|
||
vertexShaderSource: this.vertexShaderSource,
|
||
fragmentShaderSource: this.fragmentShaderSource
|
||
});
|
||
|
||
const renderState = Cesium$6.RenderState.fromCache(this.rawRenderState);
|
||
return new Cesium$6.DrawCommand({
|
||
owner: this,
|
||
vertexArray: vertexArray,
|
||
primitiveType: this.primitiveType,
|
||
uniformMap: this.uniformMap,
|
||
modelMatrix: Cesium$6.Matrix4.IDENTITY,
|
||
shaderProgram: shaderProgram,
|
||
framebuffer: this.framebuffer,
|
||
renderState: renderState,
|
||
pass: Cesium$6.Pass.OPAQUE
|
||
})
|
||
}
|
||
case "Compute": {
|
||
return new Cesium$6.ComputeCommand({
|
||
owner: this,
|
||
fragmentShaderSource: this.fragmentShaderSource,
|
||
uniformMap: this.uniformMap,
|
||
outputTexture: this.outputTexture,
|
||
persists: true
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
setGeometry(context, geometry) {
|
||
this.geometry = geometry;
|
||
const vertexArray = Cesium$6.VertexArray.fromGeometry({
|
||
context: context,
|
||
geometry: this.geometry,
|
||
attributeLocations: this.attributeLocations,
|
||
bufferUsage: Cesium$6.BufferUsage.STATIC_DRAW
|
||
});
|
||
this.commandToExecute.vertexArray = vertexArray;
|
||
}
|
||
|
||
update(frameState) {
|
||
if (!this.show) {
|
||
return
|
||
}
|
||
if (frameState.mode !== Cesium$6.SceneMode.SCENE3D) {
|
||
// 三维模式下
|
||
return
|
||
}
|
||
|
||
if (!Cesium$6.defined(this.commandToExecute)) {
|
||
this.commandToExecute = this.createCommand(frameState.context);
|
||
}
|
||
|
||
if (Cesium$6.defined(this.preExecute)) {
|
||
this.preExecute();
|
||
}
|
||
|
||
if (Cesium$6.defined(this.clearCommand)) {
|
||
frameState.commandList.push(this.clearCommand);
|
||
}
|
||
frameState.commandList.push(this.commandToExecute);
|
||
}
|
||
|
||
isDestroyed() {
|
||
return false
|
||
}
|
||
|
||
destroy() {
|
||
if (Cesium$6.defined(this.commandToExecute)) {
|
||
this.commandToExecute.shaderProgram = this.commandToExecute.shaderProgram && this.commandToExecute.shaderProgram.destroy();
|
||
}
|
||
return Cesium$6.destroyObject(this)
|
||
}
|
||
}
|
||
|
||
const Cesium$5 = mars3d__namespace.Cesium;
|
||
|
||
const Util = (function () {
|
||
const getFullscreenQuad = function () {
|
||
const fullscreenQuad = new Cesium$5.Geometry({
|
||
attributes: new Cesium$5.GeometryAttributes({
|
||
position: new Cesium$5.GeometryAttribute({
|
||
componentDatatype: Cesium$5.ComponentDatatype.FLOAT,
|
||
componentsPerAttribute: 3,
|
||
// v3----v2
|
||
// | |
|
||
// | |
|
||
// v0----v1
|
||
values: new Float32Array([
|
||
-1,
|
||
-1,
|
||
0, // v0
|
||
1,
|
||
-1,
|
||
0, // v1
|
||
1,
|
||
1,
|
||
0, // v2
|
||
-1,
|
||
1,
|
||
0 // v3
|
||
])
|
||
}),
|
||
st: new Cesium$5.GeometryAttribute({
|
||
componentDatatype: Cesium$5.ComponentDatatype.FLOAT,
|
||
componentsPerAttribute: 2,
|
||
values: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1])
|
||
})
|
||
}),
|
||
indices: new Uint32Array([3, 2, 0, 0, 2, 1])
|
||
});
|
||
return fullscreenQuad
|
||
};
|
||
|
||
const createTexture = function (options, typedArray) {
|
||
if (Cesium$5.defined(typedArray)) {
|
||
// typed array needs to be passed as source option, this is required by Cesium.Texture
|
||
const source = {};
|
||
source.arrayBufferView = typedArray;
|
||
options.source = source;
|
||
}
|
||
|
||
const texture = new Cesium$5.Texture(options);
|
||
return texture
|
||
};
|
||
|
||
const createFramebuffer = function (context, colorTexture, depthTexture) {
|
||
const framebuffer = new Cesium$5.Framebuffer({
|
||
context: context,
|
||
colorTextures: [colorTexture],
|
||
depthTexture: depthTexture
|
||
});
|
||
return framebuffer
|
||
};
|
||
|
||
const createRawRenderState = function (options) {
|
||
const translucent = true;
|
||
const closed = false;
|
||
const existing = {
|
||
viewport: options.viewport,
|
||
depthTest: options.depthTest,
|
||
depthMask: options.depthMask,
|
||
blending: options.blending
|
||
};
|
||
|
||
const rawRenderState = Cesium$5.Appearance.getDefaultRenderState(translucent, closed, existing);
|
||
return rawRenderState
|
||
};
|
||
|
||
const viewRectangleToLonLatRange = function (viewRectangle) {
|
||
const range = {};
|
||
|
||
const postiveWest = Cesium$5.Math.mod(viewRectangle.west, Cesium$5.Math.TWO_PI);
|
||
const postiveEast = Cesium$5.Math.mod(viewRectangle.east, Cesium$5.Math.TWO_PI);
|
||
const width = viewRectangle.width;
|
||
|
||
let longitudeMin;
|
||
let longitudeMax;
|
||
if (width > Cesium$5.Math.THREE_PI_OVER_TWO) {
|
||
longitudeMin = 0.0;
|
||
longitudeMax = Cesium$5.Math.TWO_PI;
|
||
} else {
|
||
if (postiveEast - postiveWest < width) {
|
||
longitudeMin = postiveWest;
|
||
longitudeMax = postiveWest + width;
|
||
} else {
|
||
longitudeMin = postiveWest;
|
||
longitudeMax = postiveEast;
|
||
}
|
||
}
|
||
|
||
range.lon = {
|
||
min: Cesium$5.Math.toDegrees(longitudeMin),
|
||
max: Cesium$5.Math.toDegrees(longitudeMax)
|
||
};
|
||
|
||
const south = viewRectangle.south;
|
||
const north = viewRectangle.north;
|
||
const height = viewRectangle.height;
|
||
|
||
const extendHeight = height > Cesium$5.Math.PI / 12 ? height / 2 : 0;
|
||
let extendedSouth = Cesium$5.Math.clampToLatitudeRange(south - extendHeight);
|
||
let extendedNorth = Cesium$5.Math.clampToLatitudeRange(north + extendHeight);
|
||
|
||
// extend the bound in high latitude area to make sure it can cover all the visible area
|
||
if (extendedSouth < -Cesium$5.Math.PI_OVER_THREE) {
|
||
extendedSouth = -Cesium$5.Math.PI_OVER_TWO;
|
||
}
|
||
if (extendedNorth > Cesium$5.Math.PI_OVER_THREE) {
|
||
extendedNorth = Cesium$5.Math.PI_OVER_TWO;
|
||
}
|
||
|
||
range.lat = {
|
||
min: Cesium$5.Math.toDegrees(extendedSouth),
|
||
max: Cesium$5.Math.toDegrees(extendedNorth)
|
||
};
|
||
|
||
return range
|
||
};
|
||
|
||
return {
|
||
getFullscreenQuad: getFullscreenQuad,
|
||
createTexture: createTexture,
|
||
createFramebuffer: createFramebuffer,
|
||
createRawRenderState: createRawRenderState,
|
||
viewRectangleToLonLatRange: viewRectangleToLonLatRange
|
||
}
|
||
})();
|
||
|
||
var segmentDraw_vert = "attribute vec2 st;\n// it is not normal itself, but used to control normal\nattribute vec3 normal; // (point to use, offset sign, not used component)\n\nuniform sampler2D currentParticlesPosition;\nuniform sampler2D postProcessingPosition;\nuniform sampler2D postProcessingSpeed;\n\nuniform float particleHeight;\n\nuniform float aspect;\nuniform float pixelSize;\nuniform float lineWidth;\n\nvarying float speedNormalization;\n\nvec3 convertCoordinate(vec3 lonLatLev) {\n // WGS84 (lon, lat, lev) -> ECEF (x, y, z)\n // see https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#From_geodetic_to_ECEF_coordinates for detail\n\n // WGS 84 geometric constants \n float a = 6378137.0; // Semi-major axis \n float b = 6356752.3142; // Semi-minor axis \n float e2 = 6.69437999014e-3; // First eccentricity squared\n\n float latitude = radians(lonLatLev.y);\n float longitude = radians(lonLatLev.x);\n\n float cosLat = cos(latitude);\n float sinLat = sin(latitude);\n float cosLon = cos(longitude);\n float sinLon = sin(longitude);\n\n float N_Phi = a / sqrt(1.0 - e2 * sinLat * sinLat);\n float h = particleHeight; // it should be high enough otherwise the particle may not pass the terrain depth test\n\n vec3 cartesian = vec3(0.0);\n cartesian.x = (N_Phi + h) * cosLat * cosLon;\n cartesian.y = (N_Phi + h) * cosLat * sinLon;\n cartesian.z = ((b * b) / (a * a) * N_Phi + h) * sinLat;\n return cartesian;\n}\n\nvec4 calcProjectedCoordinate(vec3 lonLatLev) {\n // the range of longitude in Cesium is [-180, 180] but the range of longitude in the NetCDF file is [0, 360]\n // [0, 180] is corresponding to [0, 180] and [180, 360] is corresponding to [-180, 0]\n lonLatLev.x = mod(lonLatLev.x + 180.0, 360.0) - 180.0;\n vec3 particlePosition = convertCoordinate(lonLatLev);\n vec4 projectedCoordinate = czm_modelViewProjection * vec4(particlePosition, 1.0);\n return projectedCoordinate;\n}\n\nvec4 calcOffset(vec4 currentProjectedCoordinate, vec4 nextProjectedCoordinate, float offsetSign) {\n vec2 aspectVec2 = vec2(aspect, 1.0);\n vec2 currentXY = (currentProjectedCoordinate.xy / currentProjectedCoordinate.w) * aspectVec2;\n vec2 nextXY = (nextProjectedCoordinate.xy / nextProjectedCoordinate.w) * aspectVec2;\n\n float offsetLength = lineWidth / 2.0;\n vec2 direction = normalize(nextXY - currentXY);\n vec2 normalVector = vec2(-direction.y, direction.x);\n normalVector.x = normalVector.x / aspect;\n normalVector = offsetLength * normalVector;\n\n vec4 offset = vec4(offsetSign * normalVector, 0.0, 0.0);\n return offset;\n}\n\nvoid main() {\n vec2 particleIndex = st;\n\n vec3 currentPosition = texture2D(currentParticlesPosition, particleIndex).rgb;\n vec4 nextPosition = texture2D(postProcessingPosition, particleIndex);\n\n vec4 currentProjectedCoordinate = vec4(0.0);\n vec4 nextProjectedCoordinate = vec4(0.0);\n if (nextPosition.w > 0.0) {\n currentProjectedCoordinate = calcProjectedCoordinate(currentPosition);\n nextProjectedCoordinate = calcProjectedCoordinate(currentPosition);\n } else {\n currentProjectedCoordinate = calcProjectedCoordinate(currentPosition);\n nextProjectedCoordinate = calcProjectedCoordinate(nextPosition.xyz);\n }\n\n float pointToUse = normal.x; // -1 is currentProjectedCoordinate and +1 is nextProjectedCoordinate\n float offsetSign = normal.y;\n\n vec4 offset = pixelSize * calcOffset(currentProjectedCoordinate, nextProjectedCoordinate, offsetSign);\n if (pointToUse < 0.0) {\n gl_Position = currentProjectedCoordinate + offset;\n } else {\n gl_Position = nextProjectedCoordinate + offset;\n }\n\n speedNormalization = texture2D(postProcessingSpeed, particleIndex).a;\n}"; // eslint-disable-line
|
||
|
||
var segmentDraw_frag = "uniform sampler2D colorTable;\n\nvarying float speedNormalization;\n\nvoid main() {\n gl_FragColor = texture2D(colorTable, vec2(speedNormalization, 0.0));\n}"; // eslint-disable-line
|
||
|
||
var fullscreen_vert = "attribute vec3 position;\r\nattribute vec2 st;\r\n\r\nvarying vec2 textureCoordinate;\r\n\r\nvoid main() {\r\n textureCoordinate = st;\r\n gl_Position = vec4(position, 1.0);\r\n}"; // eslint-disable-line
|
||
|
||
var trailDraw_frag = "uniform sampler2D segmentsColorTexture;\r\nuniform sampler2D segmentsDepthTexture;\r\n\r\nuniform sampler2D currentTrailsColor;\r\nuniform sampler2D trailsDepthTexture;\r\n\r\nuniform float fadeOpacity;\r\n\r\nvarying vec2 textureCoordinate;\r\n\r\nvoid main() {\r\n vec4 pointsColor = texture2D(segmentsColorTexture, textureCoordinate);\r\n vec4 trailsColor = texture2D(currentTrailsColor, textureCoordinate);\r\n\r\n trailsColor = floor(fadeOpacity * 255.0 * trailsColor) / 255.0; // make sure the trailsColor will be strictly decreased\r\n\r\n float pointsDepth = texture2D(segmentsDepthTexture, textureCoordinate).r;\r\n float trailsDepth = texture2D(trailsDepthTexture, textureCoordinate).r;\r\n float globeDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, textureCoordinate));\r\n\r\n gl_FragColor = vec4(0.0);\r\n if (pointsDepth < globeDepth) {\r\n gl_FragColor = gl_FragColor + pointsColor;\r\n }\r\n if (trailsDepth < globeDepth) {\r\n gl_FragColor = gl_FragColor + trailsColor;\r\n }\r\n gl_FragDepthEXT = min(pointsDepth, trailsDepth);\r\n}"; // eslint-disable-line
|
||
|
||
var screenDraw_frag = "uniform sampler2D trailsColorTexture;\r\nuniform sampler2D trailsDepthTexture;\r\n\r\nvarying vec2 textureCoordinate;\r\n\r\nvoid main() {\r\n vec4 trailsColor = texture2D(trailsColorTexture, textureCoordinate);\r\n float trailsDepth = texture2D(trailsDepthTexture, textureCoordinate).r;\r\n float globeDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, textureCoordinate));\r\n\r\n if (trailsDepth < globeDepth) {\r\n gl_FragColor = trailsColor;\r\n } else {\r\n gl_FragColor = vec4(0.0);\r\n }\r\n}"; // eslint-disable-line
|
||
|
||
const Cesium$4 = mars3d__namespace.Cesium;
|
||
|
||
class ParticlesRendering {
|
||
constructor(context, data, options, viewerParameters, particlesComputing) {
|
||
this.createRenderingTextures(context, data, options.colors);
|
||
this.createRenderingFramebuffers(context);
|
||
this.createRenderingPrimitives(context, options, viewerParameters, particlesComputing);
|
||
}
|
||
|
||
createRenderingTextures(context, data, colors) {
|
||
const colorTextureOptions = {
|
||
context: context,
|
||
width: context.drawingBufferWidth,
|
||
height: context.drawingBufferHeight,
|
||
pixelFormat: Cesium$4.PixelFormat.RGBA,
|
||
pixelDatatype: Cesium$4.PixelDatatype.UNSIGNED_BYTE
|
||
};
|
||
const depthTextureOptions = {
|
||
context: context,
|
||
width: context.drawingBufferWidth,
|
||
height: context.drawingBufferHeight,
|
||
pixelFormat: Cesium$4.PixelFormat.DEPTH_COMPONENT,
|
||
pixelDatatype: Cesium$4.PixelDatatype.UNSIGNED_INT
|
||
};
|
||
|
||
// 颜色处理
|
||
const colorNum = colors.length;
|
||
const colorTable = new Float32Array(colorNum * 3);
|
||
for (let i = 0; i < colorNum; i++) {
|
||
const color = Cesium$4.Color.fromCssColorString(colors[i]);
|
||
colorTable[3 * i] = color.red;
|
||
colorTable[3 * i + 1] = color.green;
|
||
colorTable[3 * i + 2] = color.blue;
|
||
}
|
||
const colorTableTextureOptions = {
|
||
context: context,
|
||
width: colorNum,
|
||
height: 1,
|
||
pixelFormat: Cesium$4.PixelFormat.RGB,
|
||
pixelDatatype: Cesium$4.PixelDatatype.FLOAT,
|
||
sampler: new Cesium$4.Sampler({
|
||
minificationFilter: Cesium$4.TextureMinificationFilter.LINEAR,
|
||
magnificationFilter: Cesium$4.TextureMagnificationFilter.LINEAR
|
||
})
|
||
};
|
||
|
||
this.textures = {
|
||
segmentsColor: Util.createTexture(colorTextureOptions),
|
||
segmentsDepth: Util.createTexture(depthTextureOptions),
|
||
|
||
currentTrailsColor: Util.createTexture(colorTextureOptions),
|
||
currentTrailsDepth: Util.createTexture(depthTextureOptions),
|
||
|
||
nextTrailsColor: Util.createTexture(colorTextureOptions),
|
||
nextTrailsDepth: Util.createTexture(depthTextureOptions),
|
||
|
||
colorTable: Util.createTexture(colorTableTextureOptions, colorTable)
|
||
};
|
||
}
|
||
|
||
createRenderingFramebuffers(context) {
|
||
this.framebuffers = {
|
||
segments: Util.createFramebuffer(context, this.textures.segmentsColor, this.textures.segmentsDepth),
|
||
currentTrails: Util.createFramebuffer(context, this.textures.currentTrailsColor, this.textures.currentTrailsDepth),
|
||
nextTrails: Util.createFramebuffer(context, this.textures.nextTrailsColor, this.textures.nextTrailsDepth)
|
||
};
|
||
}
|
||
|
||
createSegmentsGeometry(options) {
|
||
const repeatVertex = 4;
|
||
|
||
let st = [];
|
||
for (let s = 0; s < options.particlesTextureSize; s++) {
|
||
for (let t = 0; t < options.particlesTextureSize; t++) {
|
||
for (let i = 0; i < repeatVertex; i++) {
|
||
st.push(s / options.particlesTextureSize);
|
||
st.push(t / options.particlesTextureSize);
|
||
}
|
||
}
|
||
}
|
||
st = new Float32Array(st);
|
||
|
||
let normal = [];
|
||
const pointToUse = [-1, 1];
|
||
const offsetSign = [-1, 1];
|
||
for (let i = 0; i < options.maxParticles; i++) {
|
||
for (let j = 0; j < repeatVertex / 2; j++) {
|
||
for (let k = 0; k < repeatVertex / 2; k++) {
|
||
normal.push(pointToUse[j]);
|
||
normal.push(offsetSign[k]);
|
||
normal.push(0);
|
||
}
|
||
}
|
||
}
|
||
normal = new Float32Array(normal);
|
||
|
||
const indexSize = 6 * options.maxParticles;
|
||
const vertexIndexes = new Uint32Array(indexSize);
|
||
for (let i = 0, j = 0, vertex = 0; i < options.maxParticles; i++) {
|
||
vertexIndexes[j++] = vertex + 0;
|
||
vertexIndexes[j++] = vertex + 1;
|
||
vertexIndexes[j++] = vertex + 2;
|
||
vertexIndexes[j++] = vertex + 2;
|
||
vertexIndexes[j++] = vertex + 1;
|
||
vertexIndexes[j++] = vertex + 3;
|
||
vertex += 4;
|
||
}
|
||
|
||
const geometry = new Cesium$4.Geometry({
|
||
attributes: new Cesium$4.GeometryAttributes({
|
||
st: new Cesium$4.GeometryAttribute({
|
||
componentDatatype: Cesium$4.ComponentDatatype.FLOAT,
|
||
componentsPerAttribute: 2,
|
||
values: st
|
||
}),
|
||
normal: new Cesium$4.GeometryAttribute({
|
||
componentDatatype: Cesium$4.ComponentDatatype.FLOAT,
|
||
componentsPerAttribute: 3,
|
||
values: normal
|
||
})
|
||
}),
|
||
indices: vertexIndexes
|
||
});
|
||
|
||
return geometry
|
||
}
|
||
|
||
createRenderingPrimitives(context, options, viewerParameters, particlesComputing) {
|
||
const that = this;
|
||
this.primitives = {
|
||
segments: new CustomPrimitive({
|
||
commandType: "Draw",
|
||
attributeLocations: {
|
||
st: 0,
|
||
normal: 1
|
||
},
|
||
geometry: this.createSegmentsGeometry(options),
|
||
primitiveType: Cesium$4.PrimitiveType.TRIANGLES,
|
||
uniformMap: {
|
||
currentParticlesPosition: function () {
|
||
return particlesComputing.particlesTextures.currentParticlesPosition
|
||
},
|
||
postProcessingPosition: function () {
|
||
return particlesComputing.particlesTextures.postProcessingPosition
|
||
},
|
||
postProcessingSpeed: function () {
|
||
return particlesComputing.particlesTextures.postProcessingSpeed
|
||
},
|
||
colorTable: function () {
|
||
return that.textures.colorTable
|
||
},
|
||
aspect: function () {
|
||
return context.drawingBufferWidth / context.drawingBufferHeight
|
||
},
|
||
pixelSize: function () {
|
||
return viewerParameters.pixelSize
|
||
},
|
||
lineWidth: function () {
|
||
return options.lineWidth
|
||
},
|
||
particleHeight: function () {
|
||
return options.particleHeight
|
||
}
|
||
},
|
||
vertexShaderSource: new Cesium$4.ShaderSource({
|
||
sources: [segmentDraw_vert]
|
||
}),
|
||
fragmentShaderSource: new Cesium$4.ShaderSource({
|
||
sources: [segmentDraw_frag]
|
||
}),
|
||
rawRenderState: Util.createRawRenderState({
|
||
// undefined value means let Cesium deal with it
|
||
viewport: undefined,
|
||
depthTest: {
|
||
enabled: true
|
||
},
|
||
depthMask: true
|
||
}),
|
||
framebuffer: this.framebuffers.segments,
|
||
autoClear: true
|
||
}),
|
||
|
||
trails: new CustomPrimitive({
|
||
commandType: "Draw",
|
||
attributeLocations: {
|
||
position: 0,
|
||
st: 1
|
||
},
|
||
geometry: Util.getFullscreenQuad(),
|
||
primitiveType: Cesium$4.PrimitiveType.TRIANGLES,
|
||
uniformMap: {
|
||
segmentsColorTexture: function () {
|
||
return that.textures.segmentsColor
|
||
},
|
||
segmentsDepthTexture: function () {
|
||
return that.textures.segmentsDepth
|
||
},
|
||
currentTrailsColor: function () {
|
||
return that.framebuffers.currentTrails.getColorTexture(0)
|
||
},
|
||
trailsDepthTexture: function () {
|
||
return that.framebuffers.currentTrails.depthTexture
|
||
},
|
||
fadeOpacity: function () {
|
||
return options.fadeOpacity
|
||
}
|
||
},
|
||
// prevent Cesium from writing depth because the depth here should be written manually
|
||
vertexShaderSource: new Cesium$4.ShaderSource({
|
||
defines: ["DISABLE_GL_POSITION_LOG_DEPTH"],
|
||
sources: [fullscreen_vert]
|
||
}),
|
||
fragmentShaderSource: new Cesium$4.ShaderSource({
|
||
defines: ["DISABLE_LOG_DEPTH_FRAGMENT_WRITE"],
|
||
sources: [trailDraw_frag]
|
||
}),
|
||
rawRenderState: Util.createRawRenderState({
|
||
viewport: undefined,
|
||
depthTest: {
|
||
enabled: true,
|
||
func: Cesium$4.DepthFunction.ALWAYS // always pass depth test for full control of depth information
|
||
},
|
||
depthMask: true
|
||
}),
|
||
framebuffer: this.framebuffers.nextTrails,
|
||
autoClear: true,
|
||
preExecute: function () {
|
||
// swap framebuffers before binding
|
||
const temp = that.framebuffers.currentTrails;
|
||
that.framebuffers.currentTrails = that.framebuffers.nextTrails;
|
||
that.framebuffers.nextTrails = temp;
|
||
|
||
// keep the framebuffers up to date
|
||
that.primitives.trails.commandToExecute.framebuffer = that.framebuffers.nextTrails;
|
||
that.primitives.trails.clearCommand.framebuffer = that.framebuffers.nextTrails;
|
||
}
|
||
}),
|
||
|
||
screen: new CustomPrimitive({
|
||
commandType: "Draw",
|
||
attributeLocations: {
|
||
position: 0,
|
||
st: 1
|
||
},
|
||
geometry: Util.getFullscreenQuad(),
|
||
primitiveType: Cesium$4.PrimitiveType.TRIANGLES,
|
||
uniformMap: {
|
||
trailsColorTexture: function () {
|
||
return that.framebuffers.nextTrails.getColorTexture(0)
|
||
},
|
||
trailsDepthTexture: function () {
|
||
return that.framebuffers.nextTrails.depthTexture
|
||
}
|
||
},
|
||
// prevent Cesium from writing depth because the depth here should be written manually
|
||
vertexShaderSource: new Cesium$4.ShaderSource({
|
||
defines: ["DISABLE_GL_POSITION_LOG_DEPTH"],
|
||
sources: [fullscreen_vert]
|
||
}),
|
||
fragmentShaderSource: new Cesium$4.ShaderSource({
|
||
defines: ["DISABLE_LOG_DEPTH_FRAGMENT_WRITE"],
|
||
sources: [screenDraw_frag]
|
||
}),
|
||
rawRenderState: Util.createRawRenderState({
|
||
viewport: undefined,
|
||
depthTest: {
|
||
enabled: false
|
||
},
|
||
depthMask: true,
|
||
blending: {
|
||
enabled: true
|
||
}
|
||
}),
|
||
framebuffer: undefined // undefined value means let Cesium deal with it
|
||
})
|
||
};
|
||
}
|
||
}
|
||
|
||
var getWind_frag = "// the size of UV textures: width = lon, height = lat*lev\nuniform sampler2D U; // eastward wind \nuniform sampler2D V; // northward wind\n\nuniform sampler2D currentParticlesPosition; // (lon, lat, lev)\n\nuniform vec3 dimension; // (lon, lat, lev)\nuniform vec3 minimum; // minimum of each dimension\nuniform vec3 maximum; // maximum of each dimension\nuniform vec3 interval; // interval of each dimension\n\nvarying vec2 v_textureCoordinates;\n\nvec2 mapPositionToNormalizedIndex2D(vec3 lonLatLev) {\n // ensure the range of longitude and latitude\n lonLatLev.x = mod(lonLatLev.x, 360.0);\n lonLatLev.y = clamp(lonLatLev.y, -90.0, 90.0);\n\n vec3 index3D = vec3(0.0);\n index3D.x = (lonLatLev.x - minimum.x) / interval.x;\n index3D.y = (lonLatLev.y - minimum.y) / interval.y;\n index3D.z = (lonLatLev.z - minimum.z) / interval.z;\n\n // the st texture coordinate corresponding to (col, row) index\n // example\n // data array is [0, 1, 2, 3, 4, 5], width = 3, height = 2\n // the content of texture will be\n // t 1.0\n // | 3 4 5\n // |\n // | 0 1 2\n // 0.0------1.0 s\n\n vec2 index2D = vec2(index3D.x, index3D.z * dimension.y + index3D.y);\n vec2 normalizedIndex2D = vec2(index2D.x / dimension.x, index2D.y / (dimension.y * dimension.z));\n return normalizedIndex2D;\n}\n\nfloat getWind(sampler2D windTexture, vec3 lonLatLev) {\n vec2 normalizedIndex2D = mapPositionToNormalizedIndex2D(lonLatLev);\n float result = texture2D(windTexture, normalizedIndex2D).r;\n return result;\n}\n\nconst mat4 kernelMatrix = mat4(\n 0.0, -1.0, 2.0, -1.0, // first column\n 2.0, 0.0, -5.0, 3.0, // second column\n 0.0, 1.0, 4.0, -3.0, // third column\n 0.0, 0.0, -1.0, 1.0 // fourth column\n);\nfloat oneDimensionInterpolation(float t, float p0, float p1, float p2, float p3) {\n vec4 tVec4 = vec4(1.0, t, t * t, t * t * t);\n tVec4 = tVec4 / 2.0;\n vec4 pVec4 = vec4(p0, p1, p2, p3);\n return dot((tVec4 * kernelMatrix), pVec4);\n}\n\nfloat calculateB(sampler2D windTexture, float t, float lon, float lat, float lev) {\n float lon0 = floor(lon) - 1.0 * interval.x;\n float lon1 = floor(lon);\n float lon2 = floor(lon) + 1.0 * interval.x;\n float lon3 = floor(lon) + 2.0 * interval.x;\n\n float p0 = getWind(windTexture, vec3(lon0, lat, lev));\n float p1 = getWind(windTexture, vec3(lon1, lat, lev));\n float p2 = getWind(windTexture, vec3(lon2, lat, lev));\n float p3 = getWind(windTexture, vec3(lon3, lat, lev));\n\n return oneDimensionInterpolation(t, p0, p1, p2, p3);\n}\n\nfloat interpolateOneTexture(sampler2D windTexture, vec3 lonLatLev) {\n float lon = lonLatLev.x;\n float lat = lonLatLev.y;\n float lev = lonLatLev.z;\n\n float lat0 = floor(lat) - 1.0 * interval.y;\n float lat1 = floor(lat);\n float lat2 = floor(lat) + 1.0 * interval.y;\n float lat3 = floor(lat) + 2.0 * interval.y;\n\n vec2 coefficient = lonLatLev.xy - floor(lonLatLev.xy);\n float b0 = calculateB(windTexture, coefficient.x, lon, lat0, lev);\n float b1 = calculateB(windTexture, coefficient.x, lon, lat1, lev);\n float b2 = calculateB(windTexture, coefficient.x, lon, lat2, lev);\n float b3 = calculateB(windTexture, coefficient.x, lon, lat3, lev);\n\n return oneDimensionInterpolation(coefficient.y, b0, b1, b2, b3);\n}\n\nvec3 bicubic(vec3 lonLatLev) {\n // https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm\n float u = interpolateOneTexture(U, lonLatLev);\n float v = interpolateOneTexture(V, lonLatLev);\n float w = 0.0;\n return vec3(u, v, w);\n}\n\nvoid main() {\n // texture coordinate must be normalized\n vec3 lonLatLev = texture2D(currentParticlesPosition, v_textureCoordinates).rgb;\n vec3 windVector = bicubic(lonLatLev);\n gl_FragColor = vec4(windVector, 0.0);\n}"; // eslint-disable-line
|
||
|
||
var updateSpeed_frag = "uniform sampler2D currentParticlesSpeed; // (u, v, w, normalization)\nuniform sampler2D particlesWind;\n\n// used to calculate the wind norm\nuniform vec2 uSpeedRange; // (min, max);\nuniform vec2 vSpeedRange;\nuniform float pixelSize;\nuniform float speedFactor;\n\nvarying vec2 v_textureCoordinates;\n\nfloat calculateWindNorm(vec3 speed) {\n vec3 percent = vec3(0.0);\n percent.x = (speed.x - uSpeedRange.x) / (uSpeedRange.y - uSpeedRange.x);\n percent.y = (speed.y - vSpeedRange.x) / (vSpeedRange.y - vSpeedRange.x);\n float normalization = length(percent);\n\n return normalization;\n}\n\nvoid main() {\n // texture coordinate must be normalized\n vec3 currentSpeed = texture2D(currentParticlesSpeed, v_textureCoordinates).rgb;\n vec3 windVector = texture2D(particlesWind, v_textureCoordinates).rgb;\n\n vec4 nextSpeed = vec4(speedFactor * pixelSize * windVector, calculateWindNorm(windVector));\n gl_FragColor = nextSpeed;\n}"; // eslint-disable-line
|
||
|
||
var updatePosition_frag = "uniform sampler2D currentParticlesPosition; // (lon, lat, lev)\nuniform sampler2D currentParticlesSpeed; // (u, v, w, normalization)\n\nvarying vec2 v_textureCoordinates;\n\nvec2 lengthOfLonLat(vec3 lonLatLev) {\n // unit conversion: meters -> longitude latitude degrees\n // see https://en.wikipedia.org/wiki/Geographic_coordinate_system#Length_of_a_degree for detail\n\n // Calculate the length of a degree of latitude and longitude in meters\n float latitude = radians(lonLatLev.y);\n\n float term1 = 111132.92;\n float term2 = 559.82 * cos(2.0 * latitude);\n float term3 = 1.175 * cos(4.0 * latitude);\n float term4 = 0.0023 * cos(6.0 * latitude);\n float latLength = term1 - term2 + term3 - term4;\n\n float term5 = 111412.84 * cos(latitude);\n float term6 = 93.5 * cos(3.0 * latitude);\n float term7 = 0.118 * cos(5.0 * latitude);\n float longLength = term5 - term6 + term7;\n\n return vec2(longLength, latLength);\n}\n\nvoid updatePosition(vec3 lonLatLev, vec3 speed) {\n vec2 lonLatLength = lengthOfLonLat(lonLatLev);\n float u = speed.x / lonLatLength.x;\n float v = speed.y / lonLatLength.y;\n float w = 0.0;\n vec3 windVectorInLonLatLev = vec3(u, v, w);\n\n vec3 nextParticle = lonLatLev + windVectorInLonLatLev;\n\n gl_FragColor = vec4(nextParticle, 0.0);\n}\n\nvoid main() {\n // texture coordinate must be normalized\n vec3 lonLatLev = texture2D(currentParticlesPosition, v_textureCoordinates).rgb;\n vec3 speed = texture2D(currentParticlesSpeed, v_textureCoordinates).rgb;\n\n updatePosition(lonLatLev, speed);\n}"; // eslint-disable-line
|
||
|
||
var postProcessingPosition_frag = "uniform sampler2D nextParticlesPosition;\nuniform sampler2D nextParticlesSpeed; // (u, v, w, normalization)\n\n// range (min, max)\nuniform vec2 lonRange;\nuniform vec2 latRange;\n\nuniform float randomCoefficient; // use to improve the pseudo-random generator\nuniform float dropRate; // drop rate is a chance a particle will restart at random position to avoid degeneration\nuniform float dropRateBump;\n\nvarying vec2 v_textureCoordinates;\n\n// pseudo-random generator\nconst vec3 randomConstants = vec3(12.9898, 78.233, 4375.85453);\nconst vec2 normalRange = vec2(0.0, 1.0);\nfloat rand(vec2 seed, vec2 range) {\n vec2 randomSeed = randomCoefficient * seed;\n float temp = dot(randomConstants.xy, randomSeed);\n temp = fract(sin(temp) * (randomConstants.z + temp));\n return temp * (range.y - range.x) + range.x;\n}\n\nvec3 generateRandomParticle(vec2 seed, float lev) {\n // ensure the longitude is in [0, 360]\n float randomLon = mod(rand(seed, lonRange), 360.0);\n float randomLat = rand(-seed, latRange);\n\n return vec3(randomLon, randomLat, lev);\n}\n\nbool particleOutbound(vec3 particle) {\n return particle.y < -90.0 || particle.y > 90.0;\n}\n\nvoid main() {\n vec3 nextParticle = texture2D(nextParticlesPosition, v_textureCoordinates).rgb;\n vec4 nextSpeed = texture2D(nextParticlesSpeed, v_textureCoordinates);\n float particleDropRate = dropRate + dropRateBump * nextSpeed.a;\n\n vec2 seed1 = nextParticle.xy + v_textureCoordinates;\n vec2 seed2 = nextSpeed.xy + v_textureCoordinates;\n vec3 randomParticle = generateRandomParticle(seed1, nextParticle.z);\n float randomNumber = rand(seed2, normalRange);\n\n if (randomNumber < particleDropRate || particleOutbound(nextParticle)) {\n gl_FragColor = vec4(randomParticle, 1.0); // 1.0 means this is a random particle\n } else {\n gl_FragColor = vec4(nextParticle, 0.0);\n }\n}"; // eslint-disable-line
|
||
|
||
var postProcessingSpeed_frag = "uniform sampler2D postProcessingPosition;\nuniform sampler2D nextParticlesSpeed;\n\nvarying vec2 v_textureCoordinates;\n\nvoid main() {\n vec4 randomParticle = texture2D(postProcessingPosition, v_textureCoordinates);\n vec4 particleSpeed = texture2D(nextParticlesSpeed, v_textureCoordinates);\n\n if (randomParticle.a > 0.0) {\n gl_FragColor = vec4(0.0);\n } else {\n gl_FragColor = particleSpeed;\n }\n}"; // eslint-disable-line
|
||
|
||
const Cesium$3 = mars3d__namespace.Cesium;
|
||
|
||
class ParticlesComputing {
|
||
constructor(context, data, options, viewerParameters) {
|
||
this.data = data;
|
||
|
||
this.createWindTextures(context, data);
|
||
this.createParticlesTextures(context, options, viewerParameters);
|
||
this.createComputingPrimitives(data, options, viewerParameters);
|
||
}
|
||
|
||
createWindTextures(context, data) {
|
||
const windTextureOptions = {
|
||
context: context,
|
||
width: data.dimensions.lon,
|
||
height: data.dimensions.lat * (data.dimensions.lev || 1),
|
||
pixelFormat: Cesium$3.PixelFormat.LUMINANCE,
|
||
pixelDatatype: Cesium$3.PixelDatatype.FLOAT,
|
||
flipY: false,
|
||
sampler: new Cesium$3.Sampler({
|
||
// the values of texture will not be interpolated
|
||
minificationFilter: Cesium$3.TextureMinificationFilter.NEAREST,
|
||
magnificationFilter: Cesium$3.TextureMagnificationFilter.NEAREST
|
||
})
|
||
};
|
||
|
||
this.windTextures = {
|
||
U: Util.createTexture(windTextureOptions, data.U.array),
|
||
V: Util.createTexture(windTextureOptions, data.V.array)
|
||
};
|
||
}
|
||
|
||
createParticlesTextures(context, options, viewerParameters) {
|
||
const particlesTextureOptions = {
|
||
context: context,
|
||
width: options.particlesTextureSize,
|
||
height: options.particlesTextureSize,
|
||
pixelFormat: Cesium$3.PixelFormat.RGBA,
|
||
pixelDatatype: Cesium$3.PixelDatatype.FLOAT,
|
||
flipY: false,
|
||
sampler: new Cesium$3.Sampler({
|
||
// the values of texture will not be interpolated
|
||
minificationFilter: Cesium$3.TextureMinificationFilter.NEAREST,
|
||
magnificationFilter: Cesium$3.TextureMagnificationFilter.NEAREST
|
||
})
|
||
};
|
||
|
||
const particlesArray = this.randomizeParticles(options.maxParticles, viewerParameters);
|
||
const zeroArray = new Float32Array(4 * options.maxParticles).fill(0);
|
||
|
||
this.particlesTextures = {
|
||
particlesWind: Util.createTexture(particlesTextureOptions),
|
||
|
||
currentParticlesPosition: Util.createTexture(particlesTextureOptions, particlesArray),
|
||
nextParticlesPosition: Util.createTexture(particlesTextureOptions, particlesArray),
|
||
|
||
currentParticlesSpeed: Util.createTexture(particlesTextureOptions, zeroArray),
|
||
nextParticlesSpeed: Util.createTexture(particlesTextureOptions, zeroArray),
|
||
|
||
postProcessingPosition: Util.createTexture(particlesTextureOptions, particlesArray),
|
||
postProcessingSpeed: Util.createTexture(particlesTextureOptions, zeroArray)
|
||
};
|
||
}
|
||
|
||
randomizeParticles(maxParticles, viewerParameters) {
|
||
const array = new Float32Array(4 * maxParticles);
|
||
for (let i = 0; i < maxParticles; i++) {
|
||
array[4 * i] = Cesium$3.Math.randomBetween(viewerParameters.lonRange.x, viewerParameters.lonRange.y);
|
||
array[4 * i + 1] = Cesium$3.Math.randomBetween(viewerParameters.latRange.x, viewerParameters.latRange.y);
|
||
array[4 * i + 2] = Cesium$3.Math.randomBetween(this.data.lev.min, this.data.lev.max);
|
||
array[4 * i + 3] = 0.0;
|
||
}
|
||
return array
|
||
}
|
||
|
||
destroyParticlesTextures() {
|
||
Object.keys(this.particlesTextures).forEach((key) => {
|
||
this.particlesTextures[key].destroy();
|
||
});
|
||
}
|
||
|
||
createComputingPrimitives(data, options, viewerParameters) {
|
||
const dimension = new Cesium$3.Cartesian3(data.dimensions.lon, data.dimensions.lat, data.dimensions.lev);
|
||
const minimum = new Cesium$3.Cartesian3(data.lon.min, data.lat.min, data.lev.min);
|
||
const maximum = new Cesium$3.Cartesian3(data.lon.max, data.lat.max, data.lev.max);
|
||
const interval = new Cesium$3.Cartesian3(
|
||
(maximum.x - minimum.x) / (dimension.x - 1),
|
||
(maximum.y - minimum.y) / (dimension.y - 1),
|
||
dimension.z > 1 ? (maximum.z - minimum.z) / (dimension.z - 1) : 1.0
|
||
);
|
||
const uSpeedRange = new Cesium$3.Cartesian2(data.U.min, data.U.max);
|
||
const vSpeedRange = new Cesium$3.Cartesian2(data.V.min, data.V.max);
|
||
|
||
const that = this;
|
||
|
||
this.primitives = {
|
||
getWind: new CustomPrimitive({
|
||
commandType: "Compute",
|
||
uniformMap: {
|
||
U: function () {
|
||
return that.windTextures.U
|
||
},
|
||
V: function () {
|
||
return that.windTextures.V
|
||
},
|
||
currentParticlesPosition: function () {
|
||
return that.particlesTextures.currentParticlesPosition
|
||
},
|
||
dimension: function () {
|
||
return dimension
|
||
},
|
||
minimum: function () {
|
||
return minimum
|
||
},
|
||
maximum: function () {
|
||
return maximum
|
||
},
|
||
interval: function () {
|
||
return interval
|
||
}
|
||
},
|
||
fragmentShaderSource: new Cesium$3.ShaderSource({
|
||
sources: [getWind_frag]
|
||
}),
|
||
outputTexture: this.particlesTextures.particlesWind,
|
||
preExecute: function () {
|
||
// keep the outputTexture up to date
|
||
that.primitives.getWind.commandToExecute.outputTexture = that.particlesTextures.particlesWind;
|
||
}
|
||
}),
|
||
|
||
updateSpeed: new CustomPrimitive({
|
||
commandType: "Compute",
|
||
uniformMap: {
|
||
currentParticlesSpeed: function () {
|
||
return that.particlesTextures.currentParticlesSpeed
|
||
},
|
||
particlesWind: function () {
|
||
return that.particlesTextures.particlesWind
|
||
},
|
||
uSpeedRange: function () {
|
||
return uSpeedRange
|
||
},
|
||
vSpeedRange: function () {
|
||
return vSpeedRange
|
||
},
|
||
pixelSize: function () {
|
||
return viewerParameters.pixelSize
|
||
},
|
||
speedFactor: function () {
|
||
return options.speedFactor
|
||
}
|
||
},
|
||
fragmentShaderSource: new Cesium$3.ShaderSource({
|
||
sources: [updateSpeed_frag]
|
||
}),
|
||
outputTexture: this.particlesTextures.nextParticlesSpeed,
|
||
preExecute: function () {
|
||
// swap textures before binding
|
||
const temp = that.particlesTextures.currentParticlesSpeed;
|
||
that.particlesTextures.currentParticlesSpeed = that.particlesTextures.postProcessingSpeed;
|
||
that.particlesTextures.postProcessingSpeed = temp;
|
||
|
||
// keep the outputTexture up to date
|
||
that.primitives.updateSpeed.commandToExecute.outputTexture = that.particlesTextures.nextParticlesSpeed;
|
||
}
|
||
}),
|
||
|
||
updatePosition: new CustomPrimitive({
|
||
commandType: "Compute",
|
||
uniformMap: {
|
||
currentParticlesPosition: function () {
|
||
return that.particlesTextures.currentParticlesPosition
|
||
},
|
||
currentParticlesSpeed: function () {
|
||
return that.particlesTextures.currentParticlesSpeed
|
||
}
|
||
},
|
||
fragmentShaderSource: new Cesium$3.ShaderSource({
|
||
sources: [updatePosition_frag]
|
||
}),
|
||
outputTexture: this.particlesTextures.nextParticlesPosition,
|
||
preExecute: function () {
|
||
// swap textures before binding
|
||
const temp = that.particlesTextures.currentParticlesPosition;
|
||
that.particlesTextures.currentParticlesPosition = that.particlesTextures.postProcessingPosition;
|
||
that.particlesTextures.postProcessingPosition = temp;
|
||
|
||
// keep the outputTexture up to date
|
||
that.primitives.updatePosition.commandToExecute.outputTexture = that.particlesTextures.nextParticlesPosition;
|
||
}
|
||
}),
|
||
|
||
postProcessingPosition: new CustomPrimitive({
|
||
commandType: "Compute",
|
||
uniformMap: {
|
||
nextParticlesPosition: function () {
|
||
return that.particlesTextures.nextParticlesPosition
|
||
},
|
||
nextParticlesSpeed: function () {
|
||
return that.particlesTextures.nextParticlesSpeed
|
||
},
|
||
lonRange: function () {
|
||
return viewerParameters.lonRange
|
||
},
|
||
latRange: function () {
|
||
return viewerParameters.latRange
|
||
},
|
||
randomCoefficient: function () {
|
||
const randomCoefficient = Math.random();
|
||
return randomCoefficient
|
||
},
|
||
dropRate: function () {
|
||
return options.dropRate
|
||
},
|
||
dropRateBump: function () {
|
||
return options.dropRateBump
|
||
}
|
||
},
|
||
fragmentShaderSource: new Cesium$3.ShaderSource({
|
||
sources: [postProcessingPosition_frag]
|
||
}),
|
||
outputTexture: this.particlesTextures.postProcessingPosition,
|
||
preExecute: function () {
|
||
// keep the outputTexture up to date
|
||
that.primitives.postProcessingPosition.commandToExecute.outputTexture = that.particlesTextures.postProcessingPosition;
|
||
}
|
||
}),
|
||
|
||
postProcessingSpeed: new CustomPrimitive({
|
||
commandType: "Compute",
|
||
uniformMap: {
|
||
postProcessingPosition: function () {
|
||
return that.particlesTextures.postProcessingPosition
|
||
},
|
||
nextParticlesSpeed: function () {
|
||
return that.particlesTextures.nextParticlesSpeed
|
||
}
|
||
},
|
||
fragmentShaderSource: new Cesium$3.ShaderSource({
|
||
sources: [postProcessingSpeed_frag]
|
||
}),
|
||
outputTexture: this.particlesTextures.postProcessingSpeed,
|
||
preExecute: function () {
|
||
// keep the outputTexture up to date
|
||
that.primitives.postProcessingSpeed.commandToExecute.outputTexture = that.particlesTextures.postProcessingSpeed;
|
||
}
|
||
})
|
||
};
|
||
}
|
||
}
|
||
|
||
const Cesium$2 = mars3d__namespace.Cesium;
|
||
|
||
class ParticleSystem {
|
||
constructor(context, data, options, viewerParameters) {
|
||
this.context = context;
|
||
|
||
data = { ...data };
|
||
|
||
// 兼容不同格式数据
|
||
if (data.udata && data.vdata) {
|
||
data.dimensions = {};
|
||
data.dimensions.lon = data.cols;
|
||
data.dimensions.lat = data.rows;
|
||
data.dimensions.lev = data.lev || 1;
|
||
|
||
data.lon = {};
|
||
data.lon.min = data.xmin;
|
||
data.lon.max = data.xmax;
|
||
|
||
data.lat = {};
|
||
data.lat.min = data.ymin;
|
||
data.lat.max = data.ymax;
|
||
|
||
data.lev = {};
|
||
data.lev.min = data.levmin ?? 1;
|
||
data.lev.max = data.levmax ?? 1;
|
||
|
||
data.U = {};
|
||
data.U.array = new Float32Array(data.udata);
|
||
data.U.min = data.umin ?? Math.min(...data.udata);
|
||
data.U.max = data.umax ?? Math.max(...data.udata);
|
||
|
||
data.V = {};
|
||
data.V.array = new Float32Array(data.vdata);
|
||
data.V.min = data.vmin ?? Math.min(...data.vdata);
|
||
data.V.max = data.vmax ?? Math.max(...data.vdata);
|
||
}
|
||
this.data = data;
|
||
|
||
this.options = options;
|
||
this.viewerParameters = viewerParameters;
|
||
|
||
this.particlesComputing = new ParticlesComputing(this.context, this.data, this.options, this.viewerParameters);
|
||
this.particlesRendering = new ParticlesRendering(this.context, this.data, this.options, this.viewerParameters, this.particlesComputing);
|
||
}
|
||
|
||
canvasResize(context) {
|
||
this.particlesComputing.destroyParticlesTextures();
|
||
Object.keys(this.particlesComputing.windTextures).forEach((key) => {
|
||
this.particlesComputing.windTextures[key].destroy();
|
||
});
|
||
|
||
this.particlesRendering.textures.colorTable.destroy();
|
||
Object.keys(this.particlesRendering.framebuffers).forEach((key) => {
|
||
this.particlesRendering.framebuffers[key].destroy();
|
||
});
|
||
|
||
this.context = context;
|
||
this.particlesComputing = new ParticlesComputing(this.context, this.data, this.options, this.viewerParameters);
|
||
this.particlesRendering = new ParticlesRendering(this.context, this.data, this.options, this.viewerParameters, this.particlesComputing);
|
||
}
|
||
|
||
clearFramebuffers() {
|
||
const clearCommand = new Cesium$2.ClearCommand({
|
||
color: new Cesium$2.Color(0.0, 0.0, 0.0, 0.0),
|
||
depth: 1.0,
|
||
framebuffer: undefined,
|
||
pass: Cesium$2.Pass.OPAQUE
|
||
});
|
||
|
||
Object.keys(this.particlesRendering.framebuffers).forEach((key) => {
|
||
clearCommand.framebuffer = this.particlesRendering.framebuffers[key];
|
||
clearCommand.execute(this.context);
|
||
});
|
||
}
|
||
|
||
refreshParticles(maxParticlesChanged) {
|
||
this.clearFramebuffers();
|
||
|
||
this.particlesComputing.destroyParticlesTextures();
|
||
this.particlesComputing.createParticlesTextures(this.context, this.options, this.viewerParameters);
|
||
|
||
if (maxParticlesChanged) {
|
||
const geometry = this.particlesRendering.createSegmentsGeometry(this.options);
|
||
this.particlesRendering.primitives.segments.geometry = geometry;
|
||
const vertexArray = Cesium$2.VertexArray.fromGeometry({
|
||
context: this.context,
|
||
geometry: geometry,
|
||
attributeLocations: this.particlesRendering.primitives.segments.attributeLocations,
|
||
bufferUsage: Cesium$2.BufferUsage.STATIC_DRAW
|
||
});
|
||
this.particlesRendering.primitives.segments.commandToExecute.vertexArray = vertexArray;
|
||
}
|
||
}
|
||
|
||
setOptions(options) {
|
||
let maxParticlesChanged = false;
|
||
if (this.options.maxParticles !== options.maxParticles) {
|
||
maxParticlesChanged = true;
|
||
}
|
||
|
||
Object.keys(options).forEach((key) => {
|
||
this.options[key] = options[key];
|
||
});
|
||
|
||
this.refreshParticles(maxParticlesChanged);
|
||
}
|
||
|
||
applyViewerParameters(viewerParameters) {
|
||
Object.keys(viewerParameters).forEach((key) => {
|
||
this.viewerParameters[key] = viewerParameters[key];
|
||
});
|
||
|
||
this.refreshParticles(false);
|
||
}
|
||
|
||
destroy() {
|
||
clearTimeout(this.canrefresh);
|
||
|
||
this.particlesComputing.destroyParticlesTextures();
|
||
Object.keys(this.particlesComputing.windTextures).forEach((key) => {
|
||
this.particlesComputing.windTextures[key].destroy();
|
||
});
|
||
|
||
this.particlesRendering.textures.colorTable.destroy();
|
||
Object.keys(this.particlesRendering.framebuffers).forEach((key) => {
|
||
this.particlesRendering.framebuffers[key].destroy();
|
||
});
|
||
|
||
// 删除所有绑定的数据
|
||
for (const i in this) {
|
||
delete this[i];
|
||
}
|
||
}
|
||
}
|
||
|
||
const Cesium$1 = mars3d__namespace.Cesium;
|
||
|
||
const BaseLayer$1 = mars3d__namespace.layer.BaseLayer;
|
||
|
||
/**
|
||
* 风场图层, data数据结构
|
||
*
|
||
* @typedef {Object} WindLayer.DataOptions
|
||
*
|
||
* @property {Number} rows 行总数
|
||
* @property {Number} cols 列总数
|
||
* @property {Number} xmin 最小经度(度数,-180-180)
|
||
* @property {Number} xmax 最大经度(度数,-180-180)
|
||
* @property {Number} ymin 最小纬度(度数,-90-90)
|
||
* @property {Number} ymax 最大纬度(度数,-90-90)
|
||
* @property {Number[]} udata U值一维数组, 数组长度应该是 rows*cols。
|
||
* @property {Number} [umin] 最小U值
|
||
* @property {Number} [umax] 最大U值
|
||
* @property {Number[]} vdata V值一维数组, 数组长度应该是 rows*cols。
|
||
* @property {Number} [vmin] 最小v值
|
||
* @property {Number} [vmax] 最大v值
|
||
*/
|
||
|
||
const DEF_OPTIONS = {
|
||
particlesNumber: 4096,
|
||
fixedHeight: 0.0,
|
||
fadeOpacity: 0.996,
|
||
dropRate: 0.003,
|
||
dropRateBump: 0.01,
|
||
speedFactor: 0.5,
|
||
lineWidth: 2.0,
|
||
colors: ["rgb(206,255,255)"]
|
||
};
|
||
|
||
/**
|
||
* 风场图层,基于粒子实现,
|
||
* 【需要引入 mars3d-wind 插件库】
|
||
*
|
||
* @param {Object} [options] 参数对象,包括以下:
|
||
* @param {WindLayer.DataOptions} [options.data] 风场数据
|
||
* @param {Number} [options.particlesNumber =4096] 初始粒子总数
|
||
* @param {Number} [options.fadeOpacity =0.996] 消失不透明度
|
||
* @param {Number} [options.dropRate =0.003] 下降率
|
||
* @param {Number} [options.dropRateBump =0.01] 下降速度
|
||
* @param {Number} [options.speedFactor = 0.5] 速度系数
|
||
* @param {Number} [options.lineWidth =2.0] 线宽度
|
||
* @param {Number} [options.fixedHeight =0] 粒子点的固定的海拔高度
|
||
* @param {String[]} [options.colors=["rgb(206,255,255)"]] 颜色色带数组
|
||
*
|
||
* @param {String|Number} [options.id = createGuid()] 图层id标识
|
||
* @param {String|Number} [options.pid = -1] 图层父级的id,一般图层管理中使用
|
||
* @param {String} [options.name = ''] 图层名称
|
||
* @param {Boolean} [options.show = true] 图层是否显示
|
||
* @param {BaseClass|Boolean} [options.eventParent] 指定的事件冒泡对象,默认为map对象,false时不冒泡
|
||
* @param {Object} [options.center] 图层自定义定位视角 {@link Map#setCameraView}
|
||
* @param {Number} options.center.lng 经度值, 180 - 180
|
||
* @param {Number} options.center.lat 纬度值, -90 - 90
|
||
* @param {Number} [options.center.alt] 高度值
|
||
* @param {Number} [options.center.heading] 方向角度值,绕垂直于地心的轴旋转角度, 0至360
|
||
* @param {Number} [options.center.pitch] 俯仰角度值,绕纬度线旋转角度, -90至90
|
||
* @param {Number} [options.center.roll] 翻滚角度值,绕经度线旋转角度, -90至90
|
||
* @param {Boolean} [options.flyTo] 加载完成数据后是否自动飞行定位到数据所在的区域。
|
||
* @export
|
||
* @class WindLayer
|
||
* @extends {BaseLayer}
|
||
*/
|
||
class WindLayer extends BaseLayer$1 {
|
||
constructor(options = {}) {
|
||
options = { ...DEF_OPTIONS, ...options };
|
||
super(options);
|
||
|
||
this._setOptionsHook(options);
|
||
}
|
||
|
||
/**
|
||
* 存放风场粒子对象的容器
|
||
* @type {Cesium.PrimitiveCollection}
|
||
* @readonly
|
||
*/
|
||
get layer() {
|
||
return this.primitives
|
||
}
|
||
|
||
/**
|
||
* 风场数据,数据结构见类的构造方法说明
|
||
* @type {WindLayer.DataOptions}
|
||
*/
|
||
get data() {
|
||
return this._data
|
||
}
|
||
|
||
set data(value) {
|
||
this.setData(value);
|
||
}
|
||
|
||
/**
|
||
* 颜色色带数组
|
||
* @type {String[]}
|
||
*/
|
||
get colors() {
|
||
return this.options.colors
|
||
}
|
||
|
||
set colors(value) {
|
||
this.options.colors = value;
|
||
|
||
if (this.particleSystem) {
|
||
this.particleSystem.setOptions({ colors: value });
|
||
}
|
||
this.resize();
|
||
}
|
||
|
||
/**
|
||
* 对象添加到地图前创建一些对象的钩子方法,
|
||
* 只会调用一次
|
||
* @return {void} 无
|
||
* @private
|
||
*/
|
||
_mountedHook() {}
|
||
|
||
/**
|
||
* 对象添加到地图上的创建钩子方法,
|
||
* 每次add时都会调用
|
||
* @return {void} 无
|
||
* @private
|
||
*/
|
||
_addedHook() {
|
||
this.scene = this._map.scene;
|
||
this.camera = this._map.camera;
|
||
|
||
this.primitives = new Cesium$1.PrimitiveCollection();
|
||
this._map.scene.primitives.add(this.primitives);
|
||
|
||
this.viewerParameters = {
|
||
lonRange: new Cesium$1.Cartesian2(),
|
||
latRange: new Cesium$1.Cartesian2(),
|
||
pixelSize: 0.0
|
||
};
|
||
// use a smaller earth radius to make sure distance to camera > 0
|
||
this.globeBoundingSphere = new Cesium$1.BoundingSphere(Cesium$1.Cartesian3.ZERO, 0.99 * 6378137.0);
|
||
this.updateViewerParameters();
|
||
|
||
// 添加 resize 绑定事件
|
||
window.addEventListener("resize", this.resize.bind(this), false);
|
||
|
||
// 鼠标滚动、旋转后 需要重新生成风场
|
||
this.mouse_down = false;
|
||
this.mouse_move = false;
|
||
|
||
this._map.on(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
|
||
this._map.on(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
|
||
this._map.on(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
|
||
this._map.on(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
|
||
|
||
if (this._data) {
|
||
this.setData(this._data);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 对象从地图上移除的创建钩子方法,
|
||
* 每次remove时都会调用
|
||
* @return {void} 无
|
||
* @private
|
||
*/
|
||
_removedHook() {
|
||
window.removeEventListener("resize", this.resize);
|
||
this._map.off(mars3d__namespace.EventType.preRender, this._onMap_preRenderEvent, this);
|
||
|
||
this._map.off(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
|
||
this._map.off(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
|
||
this._map.off(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
|
||
this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
|
||
|
||
this.primitives.removeAll();
|
||
this._map.scene.primitives.remove(this.primitives);
|
||
}
|
||
|
||
// 更新canvas的高宽
|
||
resize() {
|
||
if (!this.show || !this.particleSystem) {
|
||
return
|
||
}
|
||
|
||
this.primitives.show = false;
|
||
this.primitives.removeAll();
|
||
|
||
this._map.once(mars3d__namespace.EventType.preRender, this._onMap_preRenderEvent, this);
|
||
}
|
||
|
||
_onMap_preRenderEvent(event) {
|
||
this.particleSystem.canvasResize(this.scene.context);
|
||
this.addPrimitives();
|
||
this.primitives.show = true;
|
||
}
|
||
|
||
// 鼠标交互
|
||
_onMapWhellEvent(event) {
|
||
clearTimeout(this.refreshTimer);
|
||
if (!this.show || !this.particleSystem) {
|
||
return
|
||
}
|
||
|
||
this.primitives.show = false;
|
||
this.refreshTimer = setTimeout(() => {
|
||
if (!this.show) {
|
||
return
|
||
}
|
||
this.redraw();
|
||
}, 200);
|
||
}
|
||
|
||
_onMouseDownEvent(event) {
|
||
this.mouse_down = true;
|
||
}
|
||
|
||
_onMouseMoveEvent(event) {
|
||
if (!this.show || !this.particleSystem) {
|
||
return
|
||
}
|
||
|
||
if (this.mouse_down) {
|
||
this.primitives.show = false;
|
||
this.mouse_move = true;
|
||
}
|
||
}
|
||
|
||
_onMouseUpEvent(event) {
|
||
if (!this.show || !this.particleSystem) {
|
||
return
|
||
}
|
||
|
||
if (this.mouse_down && this.mouse_move) {
|
||
this.redraw();
|
||
}
|
||
this.primitives.show = true;
|
||
this.mouse_down = false;
|
||
this.mouse_move = false;
|
||
}
|
||
|
||
redraw() {
|
||
if (!this._map || !this.show) {
|
||
return
|
||
}
|
||
this.updateViewerParameters();
|
||
this.particleSystem.applyViewerParameters(this.viewerParameters);
|
||
this.primitives.show = true;
|
||
}
|
||
|
||
/**
|
||
* 设置 风场数据
|
||
* @param {WindLayer.DataOptions} data 风场数据
|
||
* @return {void} 无
|
||
*/
|
||
setData(data) {
|
||
this._data = data;
|
||
|
||
if (this.particleSystem) {
|
||
this.particleSystem.destroy();
|
||
}
|
||
|
||
this.particleSystem = new ParticleSystem(this.scene.context, data, this.getOptions(), this.viewerParameters);
|
||
this.addPrimitives();
|
||
}
|
||
|
||
_setOptionsHook(options, newOptions) {
|
||
if (options) {
|
||
for (const key in options) {
|
||
this[key] = options[key];
|
||
}
|
||
}
|
||
if (this.particleSystem) {
|
||
this.particleSystem.setOptions(this.getOptions());
|
||
}
|
||
}
|
||
|
||
getOptions() {
|
||
// make sure particlesNumber is exactly the square of particlesTextureSize
|
||
const particlesTextureSize = Math.ceil(Math.sqrt(this.particlesNumber));
|
||
this.particlesNumber = particlesTextureSize * particlesTextureSize;
|
||
|
||
return {
|
||
particlesTextureSize: particlesTextureSize,
|
||
maxParticles: this.particlesNumber,
|
||
particleHeight: this.fixedHeight,
|
||
fadeOpacity: this.fadeOpacity,
|
||
dropRate: this.dropRate,
|
||
dropRateBump: this.dropRateBump,
|
||
speedFactor: this.speedFactor,
|
||
lineWidth: this.lineWidth,
|
||
globeLayer: this.globeLayer,
|
||
WMS_URL: this.WMS_URL,
|
||
colors: this.colors
|
||
}
|
||
}
|
||
|
||
addPrimitives() {
|
||
// the order of primitives.add() should respect the dependency of primitives
|
||
this.primitives.add(this.particleSystem.particlesComputing.primitives.getWind);
|
||
this.primitives.add(this.particleSystem.particlesComputing.primitives.updateSpeed);
|
||
this.primitives.add(this.particleSystem.particlesComputing.primitives.updatePosition);
|
||
this.primitives.add(this.particleSystem.particlesComputing.primitives.postProcessingPosition);
|
||
this.primitives.add(this.particleSystem.particlesComputing.primitives.postProcessingSpeed);
|
||
|
||
this.primitives.add(this.particleSystem.particlesRendering.primitives.segments);
|
||
this.primitives.add(this.particleSystem.particlesRendering.primitives.trails);
|
||
this.primitives.add(this.particleSystem.particlesRendering.primitives.screen);
|
||
}
|
||
|
||
updateViewerParameters() {
|
||
let viewRectangle = this.camera.computeViewRectangle(this.scene.globe.ellipsoid);
|
||
if (!viewRectangle) {
|
||
// 非3d模式下会为空
|
||
const extent = this._map.getExtent(); // mars3d扩展的方法
|
||
viewRectangle = Cesium$1.Rectangle.fromDegrees(extent.xmin, extent.ymin, extent.xmax, extent.ymax);
|
||
}
|
||
|
||
const lonLatRange = Util.viewRectangleToLonLatRange(viewRectangle);
|
||
this.viewerParameters.lonRange.x = lonLatRange.lon.min;
|
||
this.viewerParameters.lonRange.y = lonLatRange.lon.max;
|
||
this.viewerParameters.latRange.x = lonLatRange.lat.min;
|
||
this.viewerParameters.latRange.y = lonLatRange.lat.max;
|
||
|
||
const pixelSize = this.camera.getPixelSize(this.globeBoundingSphere, this.scene.drawingBufferWidth, this.scene.drawingBufferHeight);
|
||
|
||
if (pixelSize > 0) {
|
||
this.viewerParameters.pixelSize = pixelSize;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 注册下
|
||
mars3d__namespace.LayerUtil.register("wind", WindLayer);
|
||
|
||
mars3d__namespace.layer.WindLayer = WindLayer;
|
||
|
||
// 粒子对象
|
||
class CanvasParticle {
|
||
//= ========= 构造方法 ==========
|
||
constructor() {
|
||
this.lng = null; // 粒子初始经度
|
||
this.lat = null; // 粒子初始纬度
|
||
// this.x = null;//粒子初始x位置(相对于棋盘网格,比如x方向有360个格,x取值就是0-360,这个是初始化时随机生成的)
|
||
// this.y = null;//粒子初始y位置(同上)
|
||
this.tlng = null; // 粒子下一步将要移动的经度,这个需要计算得来
|
||
this.tlat = null; // 粒子下一步将要移动的y纬度,这个需要计算得来
|
||
this.age = null; // 粒子生命周期计时器,每次-1
|
||
// this.speed = null;//粒子移动速度,可以根据速度渲染不同颜色
|
||
}
|
||
}
|
||
|
||
// 棋盘类,根据风场数据生产风场棋盘网格
|
||
// u表示经度方向上的风,u为正,表示西风,从西边吹来的风。
|
||
// v表示纬度方向上的风,v为正,表示南风,从南边吹来的风。
|
||
class CanvasWindField {
|
||
//= ========= 构造方法 ==========
|
||
constructor(data, reverseY) {
|
||
// 行列总数
|
||
this.rows = data.rows;
|
||
this.cols = data.cols;
|
||
|
||
// 经纬度边界
|
||
this.xmin = data.xmin;
|
||
this.xmax = data.xmax;
|
||
this.ymin = data.ymin;
|
||
this.ymax = data.ymax;
|
||
|
||
this.grid = [];
|
||
|
||
const uComponent = data.udata;
|
||
const vComponent = data.vdata;
|
||
|
||
let hasGrid = false;
|
||
if (uComponent.length === this.rows && uComponent[0].length === this.cols) {
|
||
hasGrid = true; // 本身是grid方式构建的
|
||
}
|
||
|
||
let index = 0;
|
||
let arrRow = null;
|
||
let uv = null;
|
||
|
||
for (let row = 0; row < this.rows; row++) {
|
||
arrRow = [];
|
||
for (let col = 0; col < this.cols; col++, index++) {
|
||
if (hasGrid) {
|
||
uv = this._calcUV(uComponent[row][col], vComponent[row][col]);
|
||
} else {
|
||
uv = this._calcUV(uComponent[index], vComponent[index]);
|
||
}
|
||
|
||
arrRow.push(uv);
|
||
}
|
||
this.grid.push(arrRow);
|
||
}
|
||
if (reverseY) {
|
||
this.grid.reverse();
|
||
}
|
||
// console.log(this.grid);
|
||
}
|
||
|
||
// 根据经纬度,算出棋盘格位置
|
||
toGridXY(lng, lat) {
|
||
const x = ((lng - this.xmin) / (this.xmax - this.xmin)) * (this.cols - 1);
|
||
const y = ((this.ymax - lat) / (this.ymax - this.ymin)) * (this.rows - 1);
|
||
return { x, y }
|
||
}
|
||
|
||
// 根据棋盘格获取UV值
|
||
getUVByXY(x, y) {
|
||
if (x < 0 || x >= this.cols || y >= this.rows) {
|
||
return [0, 0, 0]
|
||
}
|
||
const x0 = Math.floor(x);
|
||
const y0 = Math.floor(y);
|
||
|
||
if (x0 === x && y0 === y) {
|
||
return this.grid[y][x]
|
||
}
|
||
|
||
const x1 = x0 + 1;
|
||
const y1 = y0 + 1;
|
||
|
||
const g00 = this.getUVByXY(x0, y0);
|
||
const g10 = this.getUVByXY(x1, y0);
|
||
const g01 = this.getUVByXY(x0, y1);
|
||
const g11 = this.getUVByXY(x1, y1);
|
||
let result = null;
|
||
try {
|
||
result = this._bilinearInterpolation(x - x0, y - y0, g00, g10, g01, g11);
|
||
} catch (e) {
|
||
// eslint-disable-next-line no-console
|
||
console.log(x, y);
|
||
}
|
||
return result
|
||
}
|
||
|
||
// 双线性插值算法计算给定节点的速度
|
||
// https://blog.csdn.net/qq_37577735/article/details/80041586
|
||
_bilinearInterpolation(x, y, g00, g10, g01, g11) {
|
||
const rx = 1 - x;
|
||
const ry = 1 - y;
|
||
const a = rx * ry;
|
||
const b = x * ry;
|
||
const c = rx * y;
|
||
const d = x * y;
|
||
const u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d;
|
||
const v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d;
|
||
return this._calcUV(u, v)
|
||
}
|
||
|
||
_calcUV(u, v) {
|
||
// u表示经度方向上的风,u为正,表示西风,从西边吹来的风。
|
||
// v表示纬度方向上的风,v为正,表示南风,从南边吹来的风。
|
||
return [+u, +v, Math.sqrt(u * u + v * v)] // u, v,风速
|
||
}
|
||
|
||
// 根据经纬度格获取UV值
|
||
getUVByPoint(lng, lat) {
|
||
if (!this.isInExtent(lng, lat)) {
|
||
return null
|
||
}
|
||
const gridpos = this.toGridXY(lng, lat);
|
||
const uv = this.getUVByXY(gridpos.x, gridpos.y);
|
||
return uv
|
||
}
|
||
|
||
// 粒子是否在地图范围内
|
||
isInExtent(lng, lat) {
|
||
if (lng >= this.xmin && lng <= this.xmax && lat >= this.ymin && lat <= this.ymax) {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
|
||
getRandomLatLng() {
|
||
const lng = fRandomByfloat(this.xmin, this.xmax);
|
||
const lat = fRandomByfloat(this.ymin, this.ymax);
|
||
return { lat, lng }
|
||
}
|
||
}
|
||
|
||
// 随机数生成器(小数)
|
||
function fRandomByfloat(under, over) {
|
||
return under + Math.random() * (over - under)
|
||
}
|
||
|
||
const Cesium = mars3d__namespace.Cesium;
|
||
|
||
const BaseLayer = mars3d__namespace.layer.BaseLayer;
|
||
|
||
/**
|
||
* Canvas风场图层, data数据结构
|
||
*
|
||
* @typedef {Object} CanvasWindLayer.DataOptions
|
||
*
|
||
* @property {Number} rows 行总数
|
||
* @property {Number} cols 列总数
|
||
* @property {Number} xmin 最小经度(度数,-180-180)
|
||
* @property {Number} xmax 最大经度(度数,-180-180)
|
||
* @property {Number} ymin 最小纬度(度数,-90-90)
|
||
* @property {Number} ymax 最大纬度(度数,-90-90)
|
||
* @property {Number[]|Array[]} udata U值一维数组, 数组长度应该是 rows*cols 。也支持按rows行cols列构建好的二维数组。
|
||
* @property {Number[]|Array[]} vdata V值一维数组, 数组长度应该是 rows*cols 。也支持按rows行cols列构建好的二维数组。
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* Canvas风场图层,
|
||
* 基于Canvas绘制,【需要引入 mars3d-wind 插件库】
|
||
*
|
||
* @param {Object} [options] 参数对象,包括以下:
|
||
* @param {CanvasWindLayer.DataOptions} [options.data] 风场数据
|
||
* @param {Number} [options.speedRate =50] 风前进速率,意思是将当前风场横向纵向分成100份,再乘以风速就能得到移动位置,无论地图缩放到哪一级别都是一样的速度,可以用该数值控制线流动的快慢,值越大,越慢,
|
||
* @param {Number} [options.particlesNumber =4096] 初始粒子总数
|
||
* @param {Number} [options.maxAge =120] 每个粒子的最大生存周期
|
||
* @param {Number} [options.frameRate =10] 每秒刷新次数,因为requestAnimationFrame固定每秒60次的渲染,所以如果不想这么快,就把该数值调小一些
|
||
* @param {String} [options.color ='#ffffff'] 线颜色
|
||
* @param {Number} [options.lineWidth =1] 线宽度
|
||
* @param {Number} [options.fixedHeight =0] 点的固定的海拔高度
|
||
*
|
||
* @param {Boolean} [options.reverseY =false] 是否翻转纬度数组顺序,正常数据是从北往南的(纬度从大到小),如果反向时请传reverseY为true
|
||
* @param {Boolean} [options.pointerEvents=false] 图层是否可以进行鼠标交互,为false时可以穿透操作及缩放地图
|
||
*
|
||
*
|
||
* @param {String|Number} [options.id = createGuid()] 图层id标识
|
||
* @param {String|Number} [options.pid = -1] 图层父级的id,一般图层管理中使用
|
||
* @param {String} [options.name = ''] 图层名称
|
||
* @param {Boolean} [options.show = true] 图层是否显示
|
||
* @param {BaseClass|Boolean} [options.eventParent] 指定的事件冒泡对象,默认为map对象,false时不冒泡
|
||
* @param {Object} [options.center] 图层自定义定位视角 {@link Map#setCameraView}
|
||
* @param {Number} options.center.lng 经度值, 180 - 180
|
||
* @param {Number} options.center.lat 纬度值, -90 - 90
|
||
* @param {Number} [options.center.alt] 高度值
|
||
* @param {Number} [options.center.heading] 方向角度值,绕垂直于地心的轴旋转角度, 0至360
|
||
* @param {Number} [options.center.pitch] 俯仰角度值,绕纬度线旋转角度, -90至90
|
||
* @param {Number} [options.center.roll] 翻滚角度值,绕经度线旋转角度, -90至90
|
||
* @param {Boolean} [options.flyTo] 加载完成数据后是否自动飞行定位到数据所在的区域。
|
||
*
|
||
* @export
|
||
* @class CanvasWindLayer
|
||
* @extends {BaseLayer}
|
||
*/
|
||
class CanvasWindLayer extends BaseLayer {
|
||
//= ========= 构造方法 ==========
|
||
constructor(options = {}) {
|
||
super(options);
|
||
|
||
this._setOptionsHook(options);
|
||
|
||
/**
|
||
* 图层对应的Canvas对象
|
||
* @type {HTMLCanvasElement}
|
||
* @readonly
|
||
*/
|
||
this.canvas = null;
|
||
}
|
||
|
||
_setOptionsHook(options, newOptions) {
|
||
this.calc_speedRate = [0, 0]; // 根据speedRate参数计算经纬度步进长度
|
||
this.particles = [];
|
||
this.speedRate = options.speedRate || 50;
|
||
this._particlesNumber = options.particlesNumber || 4096;
|
||
this._maxAge = options.maxAge || 120;
|
||
this.frameTime = 1000 / (options.frameRate || 10);
|
||
this._pointerEvents = this.options.pointerEvents ?? false;
|
||
|
||
/**
|
||
* 线颜色
|
||
* @type {String}
|
||
*/
|
||
this.color = options.color || "#ffffff";
|
||
|
||
/**
|
||
* 线宽度
|
||
* @type {Number}
|
||
*/
|
||
this.lineWidth = options.lineWidth || 1;
|
||
|
||
/**
|
||
* 点的固定的海拔高度
|
||
* @type {Number}
|
||
*/
|
||
this.fixedHeight = options.fixedHeight ?? 0;
|
||
|
||
/**
|
||
* 是否翻转纬度数组顺序,正常数据是从北往南的(纬度从大到小),如果反向时请传reverseY为true
|
||
* @type {Boolean}
|
||
*/
|
||
this.reverseY = options.reverseY ?? false;
|
||
}
|
||
|
||
/**
|
||
* 图层对应的Canvas对象
|
||
* @type {HTMLCanvasElement}
|
||
* @readonly
|
||
*/
|
||
get layer() {
|
||
return this.canvas
|
||
}
|
||
|
||
/**
|
||
* Canvas对象宽度(单位:像素)
|
||
* @type {Number}
|
||
* @readonly
|
||
*/
|
||
get canvasWidth() {
|
||
return this._map.scene.canvas.clientWidth
|
||
}
|
||
|
||
/**
|
||
* Canvas对象高度(单位:像素)
|
||
* @type {Number}
|
||
* @readonly
|
||
*/
|
||
get canvasHeight() {
|
||
return this._map.scene.canvas.clientHeight
|
||
}
|
||
|
||
/**
|
||
* 图层是否可以鼠标交互,为false时可以穿透操作及缩放地图
|
||
* @type {Boolean}
|
||
*/
|
||
get pointerEvents() {
|
||
return this._pointerEvents
|
||
}
|
||
|
||
set pointerEvents(value) {
|
||
this._pointerEvents = value;
|
||
if (!this.canvas) {
|
||
return
|
||
}
|
||
|
||
if (value) {
|
||
this.canvas.style["pointer-events"] = "all";
|
||
} else {
|
||
/* 加上这个css后鼠标可以穿透,但是无法触发单击等鼠标事件 */
|
||
this.canvas.style["pointer-events"] = "none";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 风前进速率,意思是将当前风场横向纵向分成100份,再乘以风速就能得到移动位置,无论地图缩放到哪一级别都是一样的速度,可以用该数值控制线流动的快慢,值越大,越慢,
|
||
* @type {Number}
|
||
*/
|
||
get speedRate() {
|
||
return this._speedRate
|
||
}
|
||
|
||
set speedRate(value) {
|
||
this._speedRate = (100 - (value > 99 ? 99 : value)) * 100;
|
||
this._calcStep();
|
||
}
|
||
|
||
/**
|
||
* 初始粒子总数
|
||
* @type {Number}
|
||
*/
|
||
get particlesNumber() {
|
||
return this._particlesNumber
|
||
}
|
||
|
||
set particlesNumber(value) {
|
||
this._particlesNumber = value;
|
||
|
||
// 不能随时刷新,需要隔一段时间刷新,避免卡顿
|
||
clearTimeout(this._canrefresh);
|
||
this._canrefresh = setTimeout(() => {
|
||
this.redraw();
|
||
}, 500);
|
||
}
|
||
|
||
/**
|
||
* 每个粒子的最大生存周期
|
||
* @type {Number}
|
||
*/
|
||
get maxAge() {
|
||
return this._maxAge
|
||
}
|
||
|
||
set maxAge(value) {
|
||
this._maxAge = value;
|
||
|
||
// 不能随时刷新,需要隔一段时间刷新,避免卡顿
|
||
clearTimeout(this._canrefresh);
|
||
this._canrefresh = setTimeout(() => {
|
||
this.redraw();
|
||
}, 500);
|
||
}
|
||
|
||
/**
|
||
* 风场数据,数据结构见类的构造方法说明
|
||
* @type {CanvasWindLayer.DataOptions}
|
||
*/
|
||
get data() {
|
||
return this.windData
|
||
}
|
||
|
||
set data(value) {
|
||
this.setData(value);
|
||
}
|
||
|
||
_showHook(val) {
|
||
if (val) {
|
||
this._addedHook();
|
||
} else {
|
||
if (this.windData) {
|
||
this.options.data = this.windData;
|
||
}
|
||
this._removedHook();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 对象添加到地图前创建一些对象的钩子方法,
|
||
* 只会调用一次
|
||
* @return {void} 无
|
||
* @private
|
||
*/
|
||
_mountedHook() {}
|
||
|
||
/**
|
||
* 对象添加到地图上的创建钩子方法,
|
||
* 每次add时都会调用
|
||
* @return {void} 无
|
||
* @private
|
||
*/
|
||
_addedHook() {
|
||
this.canvas = this._createCanvas();
|
||
this.canvasContext = this.canvas.getContext("2d"); // canvas上下文
|
||
|
||
this.bindEvent();
|
||
|
||
if (this.options.data) {
|
||
this.setData(this.options.data);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 对象从地图上移除的创建钩子方法,
|
||
* 每次remove时都会调用
|
||
* @return {void} 无
|
||
* @private
|
||
*/
|
||
_removedHook() {
|
||
this.clear();
|
||
this.unbindEvent();
|
||
|
||
if (this.canvas) {
|
||
this._map.container.removeChild(this.canvas);
|
||
delete this.canvas;
|
||
}
|
||
}
|
||
|
||
// canvas
|
||
_createCanvas() {
|
||
const windContainer = window.document.createElement("canvas");
|
||
windContainer.style.position = "absolute";
|
||
windContainer.style.top = "0px";
|
||
windContainer.style.left = "0px";
|
||
windContainer.style.width = "100%";
|
||
windContainer.style.height = "100%";
|
||
windContainer.style.pointerEvents = this._pointerEvents ? "auto" : "none"; // auto时可以交互,但是没法放大地球, none 没法交互
|
||
windContainer.style.zIndex = 10;
|
||
|
||
windContainer.setAttribute("id", "canvasWindy");
|
||
windContainer.setAttribute("class", "canvasWindy");
|
||
this._map.container.appendChild(windContainer);
|
||
|
||
const scene = this._map.scene;
|
||
windContainer.width = scene.canvas.clientWidth;
|
||
windContainer.height = scene.canvas.clientHeight;
|
||
|
||
return windContainer
|
||
}
|
||
|
||
// 更新canvas的高宽
|
||
resize() {
|
||
if (this.canvas) {
|
||
this.canvas.width = this.canvasWidth;
|
||
this.canvas.height = this.canvasHeight;
|
||
}
|
||
}
|
||
|
||
// 事件处理
|
||
bindEvent() {
|
||
const that = this;
|
||
|
||
// 更新动画
|
||
let then = Date.now()
|
||
;(function frame() {
|
||
// animateFrame: requestAnimationFrame事件句柄,用来清除操作
|
||
that.animateFrame = window.requestAnimationFrame(frame);
|
||
const now = Date.now();
|
||
const delta = now - then;
|
||
if (delta > that.frameTime) {
|
||
then = now - (delta % that.frameTime);
|
||
that.update(); // 按帧率执行
|
||
}
|
||
})();
|
||
|
||
// 添加 resize 绑定事件
|
||
window.addEventListener("resize", this.resize.bind(this), false);
|
||
|
||
// 鼠标滚动、旋转后 需要重新生成风场
|
||
this.mouse_down = false;
|
||
this.mouse_move = false;
|
||
|
||
this._map.on(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
|
||
this._map.on(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
|
||
this._map.on(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
|
||
}
|
||
|
||
unbindEvent() {
|
||
window.cancelAnimationFrame(this.animateFrame);
|
||
delete this.animateFrame;
|
||
|
||
window.removeEventListener("resize", this.resize);
|
||
|
||
this._map.off(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
|
||
this._map.off(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
|
||
this._map.off(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
|
||
this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
|
||
}
|
||
|
||
_onMapWhellEvent(event) {
|
||
clearTimeout(this.refreshTimer);
|
||
if (!this.show || !this.canvas) {
|
||
return
|
||
}
|
||
|
||
this.canvas.style.visibility = "hidden";
|
||
this.refreshTimer = setTimeout(() => {
|
||
if (!this.show) {
|
||
return
|
||
}
|
||
this.redraw();
|
||
this.canvas.style.visibility = "visible";
|
||
}, 200);
|
||
}
|
||
|
||
_onMouseDownEvent(event) {
|
||
this.mouse_down = true;
|
||
|
||
this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
|
||
this._map.on(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
|
||
}
|
||
|
||
_onMouseMoveEvent(event) {
|
||
if (!this.show || !this.canvas) {
|
||
return
|
||
}
|
||
|
||
if (this.mouse_down) {
|
||
this.canvas.style.visibility = "hidden";
|
||
this.mouse_move = true;
|
||
}
|
||
}
|
||
|
||
_onMouseUpEvent(event) {
|
||
if (!this.show || !this.canvas) {
|
||
return
|
||
}
|
||
this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
|
||
|
||
if (this.mouse_down && this.mouse_move) {
|
||
this.redraw();
|
||
}
|
||
this.canvas.style.visibility = "visible";
|
||
this.mouse_down = false;
|
||
this.mouse_move = false;
|
||
}
|
||
|
||
/**
|
||
* 重绘,根据现有参数重新生成风场
|
||
* @return {void} 无
|
||
*/
|
||
redraw() {
|
||
if (!this.show || !this.windField) {
|
||
return
|
||
}
|
||
this.particles = [];
|
||
this.drawWind();
|
||
}
|
||
|
||
/**
|
||
* 设置 风场数据
|
||
* @param {Object} data 风场数据
|
||
* @return {void} 无
|
||
*/
|
||
setData(data) {
|
||
this.clear();
|
||
this.windData = data; // 风场json数据
|
||
|
||
// 创建风场网格
|
||
this.windField = new CanvasWindField(this.windData, this.reverseY);
|
||
this.drawWind();
|
||
}
|
||
|
||
// 绘制粒子效果处理
|
||
drawWind() {
|
||
// 计算经纬度步进长度
|
||
this._calcStep();
|
||
|
||
// 创建风场粒子
|
||
for (let i = 0; i < this.particlesNumber; i++) {
|
||
const particle = this.randomParticle(new CanvasParticle());
|
||
this.particles.push(particle);
|
||
}
|
||
this.canvasContext.fillStyle = "rgba(0, 0, 0, 0.97)";
|
||
this.canvasContext.globalAlpha = 0.6;
|
||
this.update();
|
||
}
|
||
|
||
// 计算经纬度步进长度
|
||
_calcStep() {
|
||
if (!this.windField) {
|
||
return
|
||
}
|
||
|
||
this.calc_speedRate = [(this.windField.xmax - this.windField.xmin) / this.speedRate, (this.windField.ymax - this.windField.ymin) / this.speedRate];
|
||
}
|
||
|
||
update() {
|
||
if (!this.show || this.particles.length <= 0) {
|
||
return
|
||
}
|
||
|
||
let nextLng = null;
|
||
let nextLat = null;
|
||
let uv = null;
|
||
|
||
this.particles.forEach((particle) => {
|
||
if (particle.age <= 0) {
|
||
this.randomParticle(particle);
|
||
}
|
||
if (particle.age > 0) {
|
||
const tlng = particle.tlng; // 上一次的位置
|
||
const tlat = particle.tlat;
|
||
|
||
// u表示经度方向上的风,u为正,表示西风,从西边吹来的风。
|
||
// v表示纬度方向上的风,v为正,表示南风,从南边吹来的风。
|
||
uv = this.windField.getUVByPoint(tlng, tlat);
|
||
if (uv) {
|
||
nextLng = tlng + this.calc_speedRate[0] * uv[0];
|
||
nextLat = tlat + this.calc_speedRate[1] * uv[1];
|
||
|
||
particle.lng = tlng;
|
||
particle.lat = tlat;
|
||
particle.tlng = nextLng;
|
||
particle.tlat = nextLat;
|
||
particle.age--;
|
||
} else {
|
||
particle.age = 0;
|
||
}
|
||
}
|
||
});
|
||
|
||
this._drawLines();
|
||
}
|
||
|
||
// 根据粒子当前所处的位置(棋盘网格位置),计算经纬度,在根据经纬度返回屏幕坐标
|
||
_tomap(lng, lat, particle) {
|
||
const position = Cesium.Cartesian3.fromDegrees(lng, lat, this.fixedHeight);
|
||
|
||
// 判断是否在球的背面
|
||
const scene = this._map.scene;
|
||
if (scene.mode === Cesium.SceneMode.SCENE3D) {
|
||
const occluder = new Cesium.EllipsoidalOccluder(scene.globe.ellipsoid, scene.camera.positionWC);
|
||
const visible = occluder.isPointVisible(position);
|
||
// visible为true说明点在球的正面,否则点在球的背面。
|
||
// 需要注意的是不能用这种方法判断点的可见性,如果球放的比较大,点跑到屏幕外面,它返回的依然为true
|
||
if (!visible) {
|
||
particle.age = 0;
|
||
return null
|
||
}
|
||
}
|
||
// 判断是否在球的背面
|
||
|
||
const pos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this._map.scene, position);
|
||
if (pos) {
|
||
return [pos.x, pos.y]
|
||
} else {
|
||
return null
|
||
}
|
||
}
|
||
|
||
_drawLines() {
|
||
const that = this;
|
||
const particles = this.particles;
|
||
this.canvasContext.lineWidth = that.lineWidth;
|
||
// 后绘制的图形和前绘制的图形如果发生遮挡的话,只显示后绘制的图形跟前一个绘制的图形重合的前绘制的图形部分,示例:https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_globalcompop_all
|
||
this.canvasContext.globalCompositeOperation = "destination-in";
|
||
this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||
this.canvasContext.globalCompositeOperation = "lighter"; // 重叠部分的颜色会被重新计算
|
||
this.canvasContext.globalAlpha = 0.9;
|
||
this.canvasContext.beginPath();
|
||
this.canvasContext.strokeStyle = this.color;
|
||
|
||
const is2d = this._map.scene.mode !== Cesium.SceneMode.SCENE3D;
|
||
|
||
particles.forEach(function (particle) {
|
||
const movetopos = that._tomap(particle.lng, particle.lat, particle);
|
||
const linetopos = that._tomap(particle.tlng, particle.tlat, particle);
|
||
|
||
// console.log(movetopos,linetopos);
|
||
if (movetopos != null && linetopos != null) {
|
||
const step = Math.abs(movetopos[0] - linetopos[0]);
|
||
if (is2d && step >= that.canvasWidth) ; else {
|
||
that.canvasContext.moveTo(movetopos[0], movetopos[1]);
|
||
that.canvasContext.lineTo(linetopos[0], linetopos[1]);
|
||
}
|
||
}
|
||
});
|
||
this.canvasContext.stroke();
|
||
}
|
||
|
||
// 根据当前风场extent随机生成粒子
|
||
randomParticle(particle) {
|
||
let point, uv;
|
||
for (let i = 0; i < 30; i++) {
|
||
point = this.windField.getRandomLatLng();
|
||
uv = this.windField.getUVByPoint(point.lng, point.lat);
|
||
|
||
if (uv && uv[2] > 0) {
|
||
break
|
||
}
|
||
}
|
||
if (!uv) {
|
||
return particle
|
||
}
|
||
|
||
const nextLng = point.lng + this.calc_speedRate[0] * uv[0];
|
||
const nextLat = point.lat + this.calc_speedRate[1] * uv[1];
|
||
|
||
particle.lng = point.lng;
|
||
particle.lat = point.lat;
|
||
particle.tlng = nextLng;
|
||
particle.tlat = nextLat;
|
||
particle.age = Math.round(Math.random() * this.maxAge); // 每一次生成都不一样
|
||
return particle
|
||
}
|
||
|
||
/**
|
||
* 清除数据
|
||
* @return {void} 无
|
||
*/
|
||
clear() {
|
||
this.particles = [];
|
||
delete this.windField;
|
||
delete this.windData;
|
||
}
|
||
}
|
||
|
||
// 注册下
|
||
mars3d__namespace.LayerUtil.register("canvasWind", CanvasWindLayer);
|
||
|
||
mars3d__namespace.layer.CanvasWindLayer = CanvasWindLayer;
|
||
|
||
// eslint-disable-next-line no-import-assign
|
||
mars3d__namespace.WindUtil = WindUtil;
|
||
|
||
exports.CanvasWindLayer = CanvasWindLayer;
|
||
exports.WindLayer = WindLayer;
|
||
exports.WindUtil = WindUtil;
|
||
|
||
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
||
}));
|